From 1f3a3ea33748800b4faa658f41583bf854d065fc Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 16 Oct 2025 10:12:26 +0000 Subject: [PATCH 001/178] feat: nov omni draft --- configs/config_mainnet.py | 1 + scripts/vote_2025_11_17.py | 151 +++++++++++++++++++++++++++++++++++++ utils/finance.py | 14 ++++ 3 files changed, 166 insertions(+) create mode 100644 scripts/vote_2025_11_17.py diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 40768f64d..a22f09a17 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -99,6 +99,7 @@ USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" WETH_TOKEN = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" +MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" # # Lido V2 upgrade entries diff --git a/scripts/vote_2025_11_17.py b/scripts/vote_2025_11_17.py new file mode 100644 index 000000000..561122af8 --- /dev/null +++ b/scripts/vote_2025_11_17.py @@ -0,0 +1,151 @@ +""" +# Vote 2025_11_17 + +=== 1. DG PROPOPSAL === +I. Decrease Easy Track TRP limit +1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX +1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + +II. Increase SDVT target share +1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + +=== NON-DG ITEMS === +III. Transfer MATIC from Lido Treasury to Lido Labs Foundation +2. Transfer all 508,106.165781175837137177 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F + +# TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. +""" + +from typing import Dict, List, Tuple + +from utils.finance import make_matic_payout +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 get_deployer_account, get_is_live, get_priority_fee +from utils.mainnet_fork import pass_and_exec_dao_vote +from utils.dual_governance import submit_proposals +from utils.agent import agent_forward +from brownie import interface +from utils.allowed_recipients_registry import ( + update_spent_amount, + set_limit_parameters, +) + + +# ============================== Addresses =================================== +ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +LIDO_LABS_MS = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" + + +# ============================== Constants =================================== +SDVT_MODULE_ID = 2 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 410 # 4.1% +SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_MODULE_FEE_BP = 800 +SDVT_MODULE_TREASURY_FEE_BP = 200 +SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + +MATIC_AMOUNT_FOR_TRANSFER = 508_106.165781175837137177 * 10**18 + + +# ============================= Description ================================== +# TODO +IPFS_DESCRIPTION = "" + + +# ================================ Main ====================================== +def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: + + staking_router = interface.StakingRouter(STAKING_ROUTER) + + dg_items = [ + # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + agent_forward( + [update_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY)] + ), + # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + agent_forward( + [ + set_limit_parameters( + limit=11_000_000 * 10**18, + period_duration_months=12, + registry_address=ET_TRP_REGISTRY, + ), + ] + ), + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + agent_forward( + [ + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + SDVT_MODULE_ID, + SDVT_MODULE_NEW_TARGET_SHARE_BP, + SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_MODULE_FEE_BP, + SDVT_MODULE_TREASURY_FEE_BP, + SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, + SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ) + ] + ), + ] + + dg_call_script = submit_proposals([ + (dg_items, "TODO DG proposal description") + ]) + + vote_desc_items, call_script_items = zip( + ( + "TODO 1. DG submission description", + dg_call_script[0] + ), + ( + "2. Transfer all 508,106.165781175837137177 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F", + make_matic_payout( + target_address=LIDO_LABS_MS, + matic_in_wei=MATIC_AMOUNT_FOR_TRANSFER, + reference="Transfer 508,106.165781175837137177 MATIC from Treasury to Lido Labs Foundation multisig", + ), + ) + ) + + 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)) + + desc_ipfs = ( + calculate_vote_ipfs_description(IPFS_DESCRIPTION) + if silent else upload_vote_ipfs_description(IPFS_DESCRIPTION) + ) + + vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list( + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) + ) + + return vote_id, tx + + +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}.") + + +def start_and_execute_vote_on_fork_manual(): + 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) + print(f"Vote created: {vote_id}.") + pass_and_exec_dao_vote(int(vote_id), step_by_step=True) diff --git a/utils/finance.py b/utils/finance.py index 5d0cae834..d5f4acc94 100644 --- a/utils/finance.py +++ b/utils/finance.py @@ -6,6 +6,7 @@ LIDO, WETH_TOKEN, DAI_TOKEN, + MATIC_TOKEN, ) @@ -74,6 +75,19 @@ def make_dai_payout(*not_specified, target_address: str, dai_in_wei: int, refere finance=contracts.finance, ) +def make_matic_payout(*not_specified, target_address: str, matic_in_wei: int, reference: str) -> Tuple[str, str]: + """Encode MATIC payout.""" + if not_specified: + raise ValueError("Please, specify all arguments with keywords.") + + return _encode_token_transfer( + token_address=MATIC_TOKEN, + recipient=target_address, + amount=matic_in_wei, + reference=reference, + finance=contracts.finance, + ) + def _encode_token_transfer(token_address, recipient, amount, reference, finance): return (finance.address, finance.newImmediatePayment.encode_input(token_address, recipient, amount, reference)) From 687e9ca1e118de4e0e88170620339410cbfdbdad Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 17 Oct 2025 13:16:03 +0000 Subject: [PATCH 002/178] feat: pack all agent items into single agent_forward --- scripts/_vote_2025_MM_DD.py | 12 ++++----- scripts/vote_2025_11_17.py | 54 +++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/scripts/_vote_2025_MM_DD.py b/scripts/_vote_2025_MM_DD.py index a3047aa7e..5eac47ab7 100644 --- a/scripts/_vote_2025_MM_DD.py +++ b/scripts/_vote_2025_MM_DD.py @@ -39,14 +39,12 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: # TODO in case of using script based omnibus, write vote items manually # dg_items = [ - # # TODO 1.1. DG voting item 1 description # agent_forward([ - # (dg_item_address_1, dg_item_encoded_input_1) - # ]), - # # TODO 1.2. DG voting item 2 description - # agent_forward([ - # (dg_item_address_2, dg_item_encoded_input_2) - # ]), + # # TODO 1.1. DG voting item 1 description + # (dg_item_address_1, dg_item_encoded_input_1), + # # TODO 1.2. DG voting item 2 description + # (dg_item_address_2, dg_item_encoded_input_2), + # ]) # ] # # dg_call_script = submit_proposals([ diff --git a/scripts/vote_2025_11_17.py b/scripts/vote_2025_11_17.py index 561122af8..00aae3088 100644 --- a/scripts/vote_2025_11_17.py +++ b/scripts/vote_2025_11_17.py @@ -61,37 +61,31 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: staking_router = interface.StakingRouter(STAKING_ROUTER) dg_items = [ - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX - agent_forward( - [update_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY)] - ), - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX - agent_forward( - [ - set_limit_parameters( - limit=11_000_000 * 10**18, - period_duration_months=12, - registry_address=ET_TRP_REGISTRY, + agent_forward([ + # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + update_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), + + # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + set_limit_parameters( + limit=11_000_000 * 10**18, + period_duration_months=12, + registry_address=ET_TRP_REGISTRY, + ), + + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + SDVT_MODULE_ID, + SDVT_MODULE_NEW_TARGET_SHARE_BP, + SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_MODULE_FEE_BP, + SDVT_MODULE_TREASURY_FEE_BP, + SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, + SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, ), - ] - ), - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - agent_forward( - [ - ( - staking_router.address, - staking_router.updateStakingModule.encode_input( - SDVT_MODULE_ID, - SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, - SDVT_MODULE_MODULE_FEE_BP, - SDVT_MODULE_TREASURY_FEE_BP, - SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, - SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, - ), - ) - ] - ), + ), + ]) ] dg_call_script = submit_proposals([ From e1860bb4b24138aabd8096775b207b8dce836034 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 17 Oct 2025 17:49:39 +0300 Subject: [PATCH 003/178] Rename vote_2025_11_17.py to _vote_2025_11_17.py --- scripts/{vote_2025_11_17.py => _vote_2025_11_17.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{vote_2025_11_17.py => _vote_2025_11_17.py} (100%) diff --git a/scripts/vote_2025_11_17.py b/scripts/_vote_2025_11_17.py similarity index 100% rename from scripts/vote_2025_11_17.py rename to scripts/_vote_2025_11_17.py From b54dd7ecfbbc28bc337758b3c9f03298a953efc6 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 17 Oct 2025 17:53:46 +0300 Subject: [PATCH 004/178] Rename _vote_2025_11_17.py to vote_2025_11_17.py --- scripts/{_vote_2025_11_17.py => vote_2025_11_17.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{_vote_2025_11_17.py => vote_2025_11_17.py} (100%) diff --git a/scripts/_vote_2025_11_17.py b/scripts/vote_2025_11_17.py similarity index 100% rename from scripts/_vote_2025_11_17.py rename to scripts/vote_2025_11_17.py From 72dc2981b8803099922a7347e7051b7321bd6d4b Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 17 Oct 2025 16:52:22 +0000 Subject: [PATCH 005/178] fix: test template --- tests/_test_2025_MM_DD.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/tests/_test_2025_MM_DD.py b/tests/_test_2025_MM_DD.py index cb3c66413..5020f81f6 100644 --- a/tests/_test_2025_MM_DD.py +++ b/tests/_test_2025_MM_DD.py @@ -1,16 +1,19 @@ from brownie import chain, interface from brownie.network.transaction import TransactionReceipt +import pytest from utils.test.tx_tracing_helpers import ( group_voting_events_from_receipt, group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events ) from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id from utils.ipfs import get_lido_vote_cid_from_str from utils.dual_governance import PROPOSAL_STATUS from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event -) # ============================================================================ @@ -27,8 +30,10 @@ # NOTE: these addresses might have a different value on other chains VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" -DUAL_GOVERNANCE = "0xcdF49b058D606AD34c5789FD8c3BF8B3E54bA2db" +DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" # TODO Set variable to None if item is not presented EXPECTED_VOTE_ID = 1 @@ -68,6 +73,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================= Arrange variables =========================== # ======================================================================= voting = interface.Voting(VOTING) + agent = interface.Agent(AGENT) timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) @@ -114,20 +120,20 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT - assert count_vote_items_by_events(vote_tx, voting) == EXPECTED_VOTE_EVENTS_COUNT + assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT if EXPECTED_DG_PROPOSAL_ID is not None: assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - # Validate DG Proposal Submit event - validate_dual_governance_submit_event( - vote_events[0], - proposal_id=EXPECTED_DG_PROPOSAL_ID, - proposer=VOTING, - executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="TODO DG proposal description", - proposal_calls=dual_governance_proposal_calls, - emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], - ) + # TODO Validate DG Proposal Submit event + # validate_dual_governance_submit_event( + # vote_events[0], + # proposal_id=EXPECTED_DG_PROPOSAL_ID, + # proposer=VOTING, + # executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + # metadata="TODO DG proposal description", + # proposal_calls=dual_governance_proposal_calls, + # emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], + # ) # TODO validate all other voting events @@ -154,8 +160,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g timelock=EMERGENCY_PROTECTED_TIMELOCK, admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, ) - assert count_vote_items_by_events(dg_tx, agent) == EXPECTED_DG_EVENTS_COUNT - assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT # TODO validate all DG events From 797485d6771f28a055730c45cbd61496a31b5110 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 17 Oct 2025 17:04:07 +0000 Subject: [PATCH 006/178] feat: test draft --- tests/test_2025_11_17.py | 209 ++++++++++++++++++ .../test/event_validators/dual_governance.py | 11 +- 2 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 tests/test_2025_11_17.py diff --git a/tests/test_2025_11_17.py b/tests/test_2025_11_17.py new file mode 100644 index 000000000..6865bfea4 --- /dev/null +++ b/tests/test_2025_11_17.py @@ -0,0 +1,209 @@ +from brownie import chain, interface +from brownie.network.transaction import TransactionReceipt +import pytest + +from utils.test.tx_tracing_helpers import ( + group_voting_events_from_receipt, + group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events +) +from utils.evm_script import encode_call_script +from utils.voting import find_metadata_by_vote_id +from utils.ipfs import get_lido_vote_cid_from_str +from utils.dual_governance import PROPOSAL_STATUS +from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event +from utils.allowed_recipients_registry import ( + update_spent_amount, + set_limit_parameters, +) +from utils.agent import agent_forward + + +# ============================================================================ +# ============================== Import vote ================================= +# ============================================================================ +from scripts.vote_2025_11_17 import start_vote, get_vote_items + + +# ============================================================================ +# ============================== Constants =================================== +# ============================================================================ +# TODO list all contract addresses used in tests - do not use imports from config! +# NOTE: these addresses might have a different value on other chains + +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" +EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" +DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" +ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" + +# TODO Set variable to None if item is not presented +EXPECTED_VOTE_ID = 194 +EXPECTED_DG_PROPOSAL_ID = 6 +EXPECTED_VOTE_EVENTS_COUNT = 2 +EXPECTED_DG_EVENTS_COUNT = 3 +IPFS_DESCRIPTION_HASH = "" + +SDVT_MODULE_ID = 2 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 410 # 4.1% +SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_MODULE_FEE_BP = 800 +SDVT_MODULE_TREASURY_FEE_BP = 200 +SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + + +@pytest.fixture(scope="module") +def dual_governance_proposal_calls(): + + staking_router = interface.StakingRouter(STAKING_ROUTER) + + # Create all the dual governance calls that match the voting script + dg_items = [ + agent_forward([ + # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + update_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), + + # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + set_limit_parameters( + limit=11_000_000 * 10**18, + period_duration_months=12, + registry_address=ET_TRP_REGISTRY, + ), + + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + SDVT_MODULE_ID, + SDVT_MODULE_NEW_TARGET_SHARE_BP, + SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_MODULE_FEE_BP, + SDVT_MODULE_TREASURY_FEE_BP, + SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, + SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]) + ] + + + # Convert each dg_item to the expected format + proposal_calls = [] + for dg_item in dg_items: + target, data = dg_item # agent_forward returns (target, data) + proposal_calls.append({ + "target": target, + "value": 0, + "data": data + }) + + return proposal_calls + + +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + voting = interface.Voting(VOTING) + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + + + # ========================================================================= + # ======================== Identify or Create vote ======================== + # ========================================================================= + if vote_ids_from_env: + vote_id = vote_ids_from_env[0] + if EXPECTED_VOTE_ID is not None: + assert vote_id == EXPECTED_VOTE_ID + elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: + vote_id = EXPECTED_VOTE_ID + else: + vote_id, _ = start_vote({"from": ldo_holder}, silent=True) + + _, call_script_items = get_vote_items() + onchain_script = voting.getVote(vote_id)["script"] + assert onchain_script == encode_call_script(call_script_items) + + + # ========================================================================= + # ============================= Execute Vote ============================== + # ========================================================================= + is_executed = voting.getVote(vote_id)["executed"] + if not is_executed: + # ======================================================================= + # ========================= Before voting checks ======================== + # ======================================================================= + # TODO add before voting checks + + + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH + + vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) + display_voting_events(vote_tx) + vote_events = group_voting_events_from_receipt(vote_tx) + + + # ======================================================================= + # ========================= After voting checks ========================= + # ======================================================================= + # TODO add after voting tests + + + assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT + assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT + if EXPECTED_DG_PROPOSAL_ID is not None: + assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() + + # Validate DG Proposal Submit event + validate_dual_governance_submit_event( + vote_events[0], + proposal_id=EXPECTED_DG_PROPOSAL_ID, + proposer=VOTING, + executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + metadata="TODO DG proposal description", + proposal_calls=dual_governance_proposal_calls, + emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], + ) + + # TODO validate all other voting events + + + if EXPECTED_DG_PROPOSAL_ID is not None: + details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) + if details["status"] != PROPOSAL_STATUS["executed"]: + # ========================================================================= + # ================== DG before proposal executed checks =================== + # ========================================================================= + # TODO add DG before proposal executed checks + + + if details["status"] == PROPOSAL_STATUS["submitted"]: + chain.sleep(timelock.getAfterSubmitDelay() + 1) + dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + + if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: + chain.sleep(timelock.getAfterScheduleDelay() + 1) + dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + display_dg_events(dg_tx) + dg_events = group_dg_events_from_receipt( + dg_tx, + timelock=EMERGENCY_PROTECTED_TIMELOCK, + admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + ) + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + + # TODO validate all DG events + + + # ========================================================================= + # ==================== After DG proposal executed checks ================== + # ========================================================================= + # TODO add DG after proposal executed checks diff --git a/utils/test/event_validators/dual_governance.py b/utils/test/event_validators/dual_governance.py index 661354e3d..9aae57aae 100644 --- a/utils/test/event_validators/dual_governance.py +++ b/utils/test/event_validators/dual_governance.py @@ -21,12 +21,11 @@ def validate_dual_governance_submit_event( assert event["ProposalSubmitted"][0]["id"] == proposal_id, "Wrong proposalId" assert event["ProposalSubmitted"][0]["executor"] == executor, "Wrong executor" - if proposal_calls: - assert len(event["ProposalSubmitted"][0]["calls"]) == len(proposal_calls), "Wrong callsCount" - for i in range(0, len(proposal_calls)): - assert event["ProposalSubmitted"][0]["calls"][i][0] == proposal_calls[i]["target"], f"Wrong target {i}: {event['ProposalSubmitted'][0]['calls'][i][0]} : {proposal_calls[i]['target']}" - assert event["ProposalSubmitted"][0]["calls"][i][1] == proposal_calls[i]["value"], f"Wrong value {i}" - assert event["ProposalSubmitted"][0]["calls"][i][2] == proposal_calls[i]["data"], f'Wrong data {i}' + assert len(event["ProposalSubmitted"][0]["calls"]) == len(proposal_calls), "Wrong callsCount" + for i in range(0, len(proposal_calls)): + assert event["ProposalSubmitted"][0]["calls"][i][0] == proposal_calls[i]["target"], f"Wrong target {i}: {event['ProposalSubmitted'][0]['calls'][i][0]} : {proposal_calls[i]['target']}" + assert event["ProposalSubmitted"][0]["calls"][i][1] == proposal_calls[i]["value"], f"Wrong value {i}" + assert event["ProposalSubmitted"][0]["calls"][i][2] == proposal_calls[i]["data"], f'Wrong data {i}' assert event["ProposalSubmitted"][1]["proposalId"] == proposal_id, "Wrong proposalId" assert event["ProposalSubmitted"][1]["proposerAccount"] == proposer, "Wrong proposer" From 7311289ec70fe7cf7788feb84d6f1c5d60e52d00 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 17 Oct 2025 17:09:43 +0000 Subject: [PATCH 007/178] feat: update test template --- tests/_test_2025_MM_DD.py | 41 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/tests/_test_2025_MM_DD.py b/tests/_test_2025_MM_DD.py index 5020f81f6..1d66bdd67 100644 --- a/tests/_test_2025_MM_DD.py +++ b/tests/_test_2025_MM_DD.py @@ -45,26 +45,27 @@ @pytest.fixture(scope="module") def dual_governance_proposal_calls(): - # TODO list all Dual Governance proposal calls for events checking - # csm = interface.CSModule(CSM) - # staking_router = interface.StakingRouter(STAKING_ROUTER) - # csm_module_manager_role = csm.MODULE_MANAGER_ROLE() - # csm_verifier_role = csm.VERIFIER_ROLE() - # - # return [ - # { - # "target": AGENT, - # "value": 0, - # "data": agent_forward( - # [ - # ( - # dg_item_address_1, dg_item_encoded_input_1, - # ) - # ] - # )[1], - # }, - # ] - pass + # TODO Create all the dual governance calls that match the voting script + dg_items = [ + # agent_forward([ + # # TODO 1.1. DG voting item 1 description + # (dg_item_address_1, dg_item_encoded_input_1), + # # TODO 1.2. DG voting item 2 description + # (dg_item_address_2, dg_item_encoded_input_2), + # ]) + ] + + # Convert each dg_item to the expected format + proposal_calls = [] + for dg_item in dg_items: + target, data = dg_item # agent_forward returns (target, data) + proposal_calls.append({ + "target": target, + "value": 0, + "data": data + }) + + return proposal_calls def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): From 557ac2effb2c884bccb5e0cce62eea28b0b7921c Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 18 Oct 2025 18:33:13 +0000 Subject: [PATCH 008/178] fix: sdvt share limit --- configs/config_mainnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index a22f09a17..12420fed2 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -162,7 +162,7 @@ SIMPLE_DVT_ARAGON_APP_NAME = "simple-dvt" SIMPLE_DVT_ARAGON_APP_ID = "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4" SIMPLE_DVT_MODULE_STUCK_PENALTY_DELAY = 0 -SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 400 +SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 410 SIMPLE_DVT_MODULE_MODULE_FEE_BP = 800 SIMPLE_DVT_MODULE_TREASURY_FEE_BP = 200 SIMPLE_DVT_MODULE_ID = 2 From f0d67d2b8eadeaea4ac2494966706060e82e74f5 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sun, 19 Oct 2025 16:54:29 +0000 Subject: [PATCH 009/178] feat: tests --- scripts/_vote_2025_MM_DD.py | 12 +- scripts/vote_2025_11_17.py | 22 ++- tests/_test_2025_MM_DD.py | 15 +- tests/test_2025_11_17.py | 268 +++++++++++++++++++++++++- utils/test/event_validators/payout.py | 12 +- 5 files changed, 300 insertions(+), 29 deletions(-) diff --git a/scripts/_vote_2025_MM_DD.py b/scripts/_vote_2025_MM_DD.py index 5eac47ab7..a3047aa7e 100644 --- a/scripts/_vote_2025_MM_DD.py +++ b/scripts/_vote_2025_MM_DD.py @@ -39,12 +39,14 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: # TODO in case of using script based omnibus, write vote items manually # dg_items = [ + # # TODO 1.1. DG voting item 1 description # agent_forward([ - # # TODO 1.1. DG voting item 1 description - # (dg_item_address_1, dg_item_encoded_input_1), - # # TODO 1.2. DG voting item 2 description - # (dg_item_address_2, dg_item_encoded_input_2), - # ]) + # (dg_item_address_1, dg_item_encoded_input_1) + # ]), + # # TODO 1.2. DG voting item 2 description + # agent_forward([ + # (dg_item_address_2, dg_item_encoded_input_2) + # ]), # ] # # dg_call_script = submit_proposals([ diff --git a/scripts/vote_2025_11_17.py b/scripts/vote_2025_11_17.py index 00aae3088..f78648665 100644 --- a/scripts/vote_2025_11_17.py +++ b/scripts/vote_2025_11_17.py @@ -11,7 +11,7 @@ === NON-DG ITEMS === III. Transfer MATIC from Lido Treasury to Lido Labs Foundation -2. Transfer all 508,106.165781175837137177 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F +2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ @@ -40,19 +40,21 @@ # ============================== Constants =================================== SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 410 # 4.1% +SDVT_MODULE_NEW_TARGET_SHARE_BP = 410 SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 SDVT_MODULE_MODULE_FEE_BP = 800 SDVT_MODULE_TREASURY_FEE_BP = 200 SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 -MATIC_AMOUNT_FOR_TRANSFER = 508_106.165781175837137177 * 10**18 +NODE_OPERATORS_REGISTRY_ID = 1 + +MATIC_FOR_TRANSFER = 508_106 * 10**18 # ============================= Description ================================== # TODO -IPFS_DESCRIPTION = "" +IPFS_DESCRIPTION = "omni nov 2025" # ================================ Main ====================================== @@ -64,14 +66,16 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: agent_forward([ # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX update_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), - + ]), + agent_forward([ # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX set_limit_parameters( limit=11_000_000 * 10**18, period_duration_months=12, registry_address=ET_TRP_REGISTRY, ), - + ]), + agent_forward([ # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, @@ -98,11 +102,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_call_script[0] ), ( - "2. Transfer all 508,106.165781175837137177 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F", + "2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F", make_matic_payout( target_address=LIDO_LABS_MS, - matic_in_wei=MATIC_AMOUNT_FOR_TRANSFER, - reference="Transfer 508,106.165781175837137177 MATIC from Treasury to Lido Labs Foundation multisig", + matic_in_wei=MATIC_FOR_TRANSFER, + reference="Transfer 508,106 MATIC from Treasury to Lido Labs Foundation multisig", ), ) ) diff --git a/tests/_test_2025_MM_DD.py b/tests/_test_2025_MM_DD.py index 1d66bdd67..7c01f66ed 100644 --- a/tests/_test_2025_MM_DD.py +++ b/tests/_test_2025_MM_DD.py @@ -47,12 +47,14 @@ def dual_governance_proposal_calls(): # TODO Create all the dual governance calls that match the voting script dg_items = [ - # agent_forward([ - # # TODO 1.1. DG voting item 1 description - # (dg_item_address_1, dg_item_encoded_input_1), - # # TODO 1.2. DG voting item 2 description - # (dg_item_address_2, dg_item_encoded_input_2), - # ]) + # # TODO 1.1. DG voting item 1 description + # agent_forward([ + # (dg_item_address_1, dg_item_encoded_input_1) + # ]), + # # TODO 1.2. DG voting item 2 description + # agent_forward([ + # (dg_item_address_2, dg_item_encoded_input_2) + # ]), ] # Convert each dg_item to the expected format @@ -162,6 +164,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, ) assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT # TODO validate all DG events diff --git a/tests/test_2025_11_17.py b/tests/test_2025_11_17.py index 6865bfea4..32d640fe5 100644 --- a/tests/test_2025_11_17.py +++ b/tests/test_2025_11_17.py @@ -1,6 +1,7 @@ from brownie import chain, interface from brownie.network.transaction import TransactionReceipt import pytest +from brownie.network.account import LocalAccount from utils.test.tx_tracing_helpers import ( group_voting_events_from_receipt, @@ -9,6 +10,7 @@ display_voting_events, display_dg_events ) +from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id from utils.ipfs import get_lido_vote_cid_from_str @@ -19,6 +21,11 @@ set_limit_parameters, ) from utils.agent import agent_forward +from utils.test.event_validators.payout import ( + validate_token_payout_event, + Payout, +) +from utils.test.deposits_helpers import fill_deposit_buffer, drain_remained_buffered_ether # ============================================================================ @@ -40,21 +47,35 @@ DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +LIDO_LABS_MS = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" +DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" +ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" +DEPOSIT_SECURITY_MODULE = "0xffa96d84def2ea035c7ab153d8b991128e3d72fd" +LIDO = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" +SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" # TODO Set variable to None if item is not presented EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 EXPECTED_VOTE_EVENTS_COUNT = 2 EXPECTED_DG_EVENTS_COUNT = 3 -IPFS_DESCRIPTION_HASH = "" +IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 410 # 4.1% +SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 410 SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 SDVT_MODULE_MODULE_FEE_BP = 800 SDVT_MODULE_TREASURY_FEE_BP = 200 SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +SDVT_MODULE_NAME = "SimpleDVT" + +MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" +MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 +MATIC_IN_TREASURY_AFTER = 165_781_175_837_137_177 +MATIC_IN_LIDO_LABS_BEFORE = 0 +MATIC_IN_LIDO_LABS_AFTER = 508_106_000_000_000_000_000_000 @pytest.fixture(scope="module") @@ -65,17 +86,16 @@ def dual_governance_proposal_calls(): # Create all the dual governance calls that match the voting script dg_items = [ agent_forward([ - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX update_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), - - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + ]), + agent_forward([ set_limit_parameters( limit=11_000_000 * 10**18, period_duration_months=12, registry_address=ET_TRP_REGISTRY, ), - - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + ]), + agent_forward([ ( staking_router.address, staking_router.updateStakingModule.encode_input( @@ -114,6 +134,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g agent = interface.Agent(AGENT) timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + matic_token = interface.ERC20(MATIC_TOKEN) + staking_router = interface.StakingRouter(STAKING_ROUTER) # ========================================================================= @@ -143,6 +165,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # TODO add before voting checks + # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F + matic_treasury_balance_before = matic_token.balanceOf(agent.address) + assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE + matic_labs_balance_before = matic_token.balanceOf(LIDO_LABS_MS) + assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH @@ -156,6 +184,15 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # TODO add after voting tests + # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F + matic_treasury_balance_after = matic_token.balanceOf(agent.address) + assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER + matic_labs_balance_after = matic_token.balanceOf(LIDO_LABS_MS) + assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER + # make sure Lido Labs can actually spend the received MATIC + matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LIDO_LABS_MS}) + assert matic_token.balanceOf(LIDO_LABS_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 + assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT @@ -175,6 +212,17 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # TODO validate all other voting events + validate_token_payout_event( + event=vote_events[1], + p=Payout( + token_addr=MATIC_TOKEN, + from_addr=AGENT, + to_addr=LIDO_LABS_MS, + amount=MATIC_IN_LIDO_LABS_AFTER), + is_steth=False, + emitted_by=AGENT + ) + if EXPECTED_DG_PROPOSAL_ID is not None: details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) @@ -184,6 +232,17 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # TODO add DG before proposal executed checks + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) + assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP + assert sdvt_module_before['id'] == SDVT_MODULE_ID + assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert sdvt_module_before['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + assert sdvt_module_before['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + assert sdvt_module_before['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert sdvt_module_before['name'] == SDVT_MODULE_NAME + if details["status"] == PROPOSAL_STATUS["submitted"]: chain.sleep(timelock.getAfterSubmitDelay() + 1) @@ -199,11 +258,206 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, ) assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT # TODO validate all DG events + validate_staking_module_update_event( + event=dg_events[2], + module_item=StakingModuleItem( + id=SDVT_MODULE_ID, + name=SDVT_MODULE_NAME, + address=None, + target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, + module_fee=SDVT_MODULE_MODULE_FEE_BP, + treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, + priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + emitted_by=STAKING_ROUTER, + is_dg_event=True + ) # ========================================================================= # ==================== After DG proposal executed checks ================== # ========================================================================= # TODO add DG after proposal executed checks + + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) + assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP + assert sdvt_module_after['id'] == SDVT_MODULE_ID + assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert sdvt_module_after['name'] == SDVT_MODULE_NAME + # additional checks to make sure no other fields were changed + assert sdvt_module_after['id'] == sdvt_module_before['id'] + assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] + assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] + assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] + assert sdvt_module_after['status'] == sdvt_module_before['status'] + assert sdvt_module_after['name'] == sdvt_module_before['name'] + assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] + assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] + assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] + assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] + assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] + assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) + assert len(sdvt_module_after.items()) == 13 + + +# additional tests for SDVT module behavior after the vote +# TODO fix test and enable +def _test_stake_allocation_after_voting(accounts, helpers, ldo_holder, vote_ids_from_env): + evm_script_executor: LocalAccount = accounts.at(ET_EVM_SCRIPT_EXECUTOR, force=True) + sdvt_remaining_cap_before: int = get_staking_module_remaining_cap(SDVT_MODULE_ID) + check_alloc_keys = sdvt_remaining_cap_before + + # Fill the module with keys. Keep last nop_id to add more keys to other node operators later + last_nop_id = fill_sdvt_module_with_keys( + evm_script_executor=evm_script_executor, total_keys=sdvt_remaining_cap_before + ) + + _, sdvt_allocation_percentage_after_filling = get_allocation_percentage(check_alloc_keys) + + assert sdvt_allocation_percentage_after_filling == 0.5 # 0.5% of total allocated keys — current target share + + # add more keys to the module to check that percentage wasn't changed + last_nop_id = fill_sdvt_module_with_keys( + evm_script_executor=evm_script_executor, total_keys=200, start_nop_id=last_nop_id - 1 + ) + + _, sdvt_allocation_percentage_after = get_allocation_percentage(check_alloc_keys + 200) + + assert ( + sdvt_allocation_percentage_after == sdvt_allocation_percentage_after_filling + ) # share percentage should not change after second filling + + # VOTE! + vote_id = vote_ids_from_env[0] if vote_ids_from_env else start_vote({"from": ldo_holder}, silent=True)[0] + helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=VOTING, skip_time=3 * 60 * 60 * 24) + + # add more keys to the module + fill_sdvt_module_with_keys(evm_script_executor=evm_script_executor, total_keys=300, start_nop_id=last_nop_id - 1) + + sdvt_remaining_cap_after_vote = get_staking_module_remaining_cap(SDVT_MODULE_ID) + + assert sdvt_remaining_cap_after_vote > sdvt_remaining_cap_before # remaining cap should increase after the vote + + _, sdvt_allocation_percentage_after_vote = get_allocation_percentage(sdvt_remaining_cap_after_vote) + + assert ( + sdvt_allocation_percentage_after_vote > sdvt_allocation_percentage_after_filling + ) # allocation percentage should increase after the vote + + +def fill_sdvt_module(): + staking_router = interface.StakingRouter(STAKING_ROUTER) + lido = interface.Lido(LIDO) + + sdvt_module_stats = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) + keys_to_allocate = sdvt_module_stats["depositableValidatorsCount"] + fill_deposit_buffer(keys_to_allocate) + lido.deposit(keys_to_allocate, SDVT_MODULE_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) + +# TODO fix test and enable +def _test_sdvt_stake_allocation(accounts, helpers, ldo_holder, vote_ids_from_env): + staking_router = interface.StakingRouter(STAKING_ROUTER) + lido = interface.Lido(LIDO) + + evm_script_executor: LocalAccount = accounts.at(ET_EVM_SCRIPT_EXECUTOR, force=True) + new_sdvt_keys_amount = 60 + + # prepare initial state + fill_sdvt_module() + drain_remained_buffered_ether() + fill_deposit_buffer(200) + + nor_module_stats_before = staking_router.getStakingModuleSummary(NODE_OPERATORS_REGISTRY_ID) + sdvt_module_stats_before = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) + + # VOTE! + vote_id = vote_ids_from_env[0] if vote_ids_from_env else start_vote({"from": ldo_holder}, silent=True)[0] + helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=VOTING, skip_time=3 * 60 * 60 * 24) + + # No new keys in the SDVT module + lido.deposit(100, NODE_OPERATORS_REGISTRY_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) + lido.deposit(100, SDVT_MODULE_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) + nor_module_stats_after_vote = staking_router.getStakingModuleSummary(NODE_OPERATORS_REGISTRY_ID) + sdvt_module_stats_after_vote = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) + + assert ( + sdvt_module_stats_after_vote["totalDepositedValidators"] - sdvt_module_stats_before["totalDepositedValidators"] + == 0 + ), "No new keys should go to the SDVT module" + assert ( + nor_module_stats_after_vote["totalDepositedValidators"] - nor_module_stats_before["totalDepositedValidators"] + == 100 + ), "All keys should go to the NOR module" + + # Add new keys to the SDVT module + fill_sdvt_module_with_keys(evm_script_executor=evm_script_executor, total_keys=new_sdvt_keys_amount) + + lido.deposit(100, SDVT_MODULE_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) + lido.deposit(100, NODE_OPERATORS_REGISTRY_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) + nor_module_stats_after = staking_router.getStakingModuleSummary(NODE_OPERATORS_REGISTRY_ID) + sdvt_module_stats_after = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) + + assert sdvt_module_stats_after["depositableValidatorsCount"] == 0, "All accessible keys should be deposited" + assert ( + sdvt_module_stats_after["totalDepositedValidators"] + == sdvt_module_stats_after_vote["totalDepositedValidators"] + new_sdvt_keys_amount + ), f"{new_sdvt_keys_amount} keys should go to the SDVT module" + assert nor_module_stats_after["totalDepositedValidators"] == nor_module_stats_after_vote[ + "totalDepositedValidators" + ] + (100 - new_sdvt_keys_amount), "All other keys should go to the NOR module" + + +def fill_sdvt_module_with_keys(evm_script_executor: LocalAccount, total_keys: int, start_nop_id: int = 0) -> int: + simple_dvt = interface.SimpleDVT(SDVT) + + if start_nop_id == 0: + start_nop_id = simple_dvt.getNodeOperatorsCount() - 1 + nop_id = start_nop_id + + keys_to_allocate = ( + total_keys if total_keys < 100 else 100 + ) # keys to allocate to each node operator, base batch is 100 keys per operator + steps = 1 if keys_to_allocate == total_keys else (total_keys // keys_to_allocate) + 1 + for idx in range(steps): + nop_id = start_nop_id - idx + simple_dvt_add_keys(simple_dvt, nop_id, keys_to_allocate) + simple_dvt.setNodeOperatorStakingLimit(nop_id, keys_to_allocate, {"from": evm_script_executor}) + + return nop_id + + +def get_staking_module_remaining_cap(staking_module_id: int) -> int: + staking_router = interface.StakingRouter(STAKING_ROUTER) + + module_summary = staking_router.getStakingModuleSummary(staking_module_id) + module_active_keys = module_summary["totalDepositedValidators"] - module_summary["totalExitedValidators"] + + all_modules_summary = [ + staking_router.getStakingModuleSummary(module_id) + for module_id in [NODE_OPERATORS_REGISTRY_ID, SDVT_MODULE_ID] + ] + total_active_keys = sum( + module_summary["totalDepositedValidators"] - module_summary["totalExitedValidators"] + for module_summary in all_modules_summary + ) + + target_share = staking_router.getStakingModule(staking_module_id)["targetShare"] + + return math.ceil((target_share * total_active_keys) / TOTAL_BASIS_POINTS) - module_active_keys + + +def get_allocation_percentage(keys_to_allocate: int) -> (float, float): + staking_router = interface.StakingRouter(STAKING_ROUTER) + + allocation = staking_router.getDepositsAllocation(keys_to_allocate) + total_allocated = sum(allocation["allocations"]) + return (round((allocation["allocations"][0] / total_allocated) * TOTAL_BASIS_POINTS) / 100), ( + round((allocation["allocations"][1] / total_allocated) * TOTAL_BASIS_POINTS) / 100 + ) diff --git a/utils/test/event_validators/payout.py b/utils/test/event_validators/payout.py index 1aa084cf0..a810c1386 100644 --- a/utils/test/event_validators/payout.py +++ b/utils/test/event_validators/payout.py @@ -5,6 +5,7 @@ from brownie.network.event import EventDict from .common import validate_events_chain from utils.finance import ZERO_ADDRESS +from brownie import convert class Payout(NamedTuple): @@ -14,13 +15,15 @@ class Payout(NamedTuple): amount: int -def validate_token_payout_event(event: EventDict, p: Payout, is_steth: bool = False): - _token_events_chain = ["LogScriptCall", "NewPeriod", "NewTransaction", "Transfer"] +def validate_token_payout_event(event: EventDict, p: Payout, is_steth: bool = False, emitted_by: str = None): + _token_events_chain = ["LogScriptCall", "NewPeriod", "NewPeriod", "NewTransaction", "Transfer"] if is_steth: _token_events_chain += ["TransferShares"] _token_events_chain += ["VaultTransfer"] + print (event) + validate_events_chain([e.name for e in event], _token_events_chain) assert event.count("VaultTransfer") == 1 @@ -49,6 +52,11 @@ def validate_token_payout_event(event: EventDict, p: Payout, is_steth: bool = Fa assert event["NewTransaction"]["entity"] == p.to_addr assert event["NewTransaction"]["amount"] == p.amount + if emitted_by is not None: + assert convert.to_address(event["VaultTransfer"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" + def validate_ether_payout_event(event: EventDict, p: Payout): _ether_events_chain = ["LogScriptCall", "NewPeriod", "NewTransaction", "VaultTransfer"] From b78c0d681320ee633b23d91ee42cc7176e6ae9a4 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sun, 19 Oct 2025 20:45:37 +0000 Subject: [PATCH 010/178] feat: tests (ii) --- scripts/vote_2025_11_17.py | 4 +- tests/test_2025_11_17.py | 119 +++++++++++++++++- .../allowed_recipients_registry.py | 13 +- 3 files changed, 128 insertions(+), 8 deletions(-) diff --git a/scripts/vote_2025_11_17.py b/scripts/vote_2025_11_17.py index f78648665..6c5fea832 100644 --- a/scripts/vote_2025_11_17.py +++ b/scripts/vote_2025_11_17.py @@ -27,7 +27,7 @@ from utils.agent import agent_forward from brownie import interface from utils.allowed_recipients_registry import ( - update_spent_amount, + unsafe_set_spent_amount, set_limit_parameters, ) @@ -65,7 +65,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_items = [ agent_forward([ # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX - update_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), + unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), ]), agent_forward([ # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX diff --git a/tests/test_2025_11_17.py b/tests/test_2025_11_17.py index 32d640fe5..340fdc3fc 100644 --- a/tests/test_2025_11_17.py +++ b/tests/test_2025_11_17.py @@ -1,4 +1,4 @@ -from brownie import chain, interface +from brownie import chain, interface, reverts, accounts from brownie.network.transaction import TransactionReceipt import pytest from brownie.network.account import LocalAccount @@ -10,6 +10,7 @@ display_voting_events, display_dg_events ) +from utils.test.easy_track_helpers import create_and_enact_payment_motion from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id @@ -17,7 +18,7 @@ from utils.dual_governance import PROPOSAL_STATUS from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event from utils.allowed_recipients_registry import ( - update_spent_amount, + unsafe_set_spent_amount, set_limit_parameters, ) from utils.agent import agent_forward @@ -26,6 +27,10 @@ Payout, ) from utils.test.deposits_helpers import fill_deposit_buffer, drain_remained_buffered_ether +from utils.test.event_validators.allowed_recipients_registry import ( + validate_set_limit_parameter_event, + validate_set_spent_amount_event, +) # ============================================================================ @@ -53,6 +58,10 @@ DEPOSIT_SECURITY_MODULE = "0xffa96d84def2ea035c7ab153d8b991128e3d72fd" LIDO = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" +EASY_TRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" +TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" +TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" +LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" # TODO Set variable to None if item is not presented EXPECTED_VOTE_ID = 194 @@ -75,7 +84,15 @@ MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 MATIC_IN_TREASURY_AFTER = 165_781_175_837_137_177 MATIC_IN_LIDO_LABS_BEFORE = 0 -MATIC_IN_LIDO_LABS_AFTER = 508_106_000_000_000_000_000_000 +MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 + +TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 +TRP_ALREADY_SPENT_BEFORE = 2_676_801 * 10**18 +TRP_ALREADY_SPENT_AFTER = 0 +TRP_LIMIT_AFTER = 11_000_000 * 10**18 +TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC +TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC +TRP_PERIOD_DURATION_MONTHS = 12 @pytest.fixture(scope="module") @@ -86,11 +103,11 @@ def dual_governance_proposal_calls(): # Create all the dual governance calls that match the voting script dg_items = [ agent_forward([ - update_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), + unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), ]), agent_forward([ set_limit_parameters( - limit=11_000_000 * 10**18, + limit=TRP_LIMIT_AFTER, period_duration_months=12, registry_address=ET_TRP_REGISTRY, ), @@ -136,6 +153,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) matic_token = interface.ERC20(MATIC_TOKEN) staking_router = interface.StakingRouter(STAKING_ROUTER) + et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) # ========================================================================= @@ -232,6 +250,17 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # TODO add DG before proposal executed checks + # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() + trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() + assert trp_limit_before == TRP_LIMIT_BEFORE + assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS + assert trp_already_spent_amount_before == TRP_ALREADY_SPENT_BEFORE + assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - TRP_ALREADY_SPENT_BEFORE + assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP + assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP @@ -262,6 +291,22 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # TODO validate all DG events + validate_set_spent_amount_event( + dg_events[0], + new_spent_amount=0, + emitted_by=ET_TRP_REGISTRY, + is_dg_event=True, + ) + + validate_set_limit_parameter_event( + dg_events[1], + limit=TRP_LIMIT_AFTER, + period_duration_month=TRP_PERIOD_DURATION_MONTHS, + period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, + emitted_by=ET_TRP_REGISTRY, + is_dg_event=True, + ) + validate_staking_module_update_event( event=dg_events[2], module_item=StakingModuleItem( @@ -281,6 +326,17 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # TODO add DG after proposal executed checks + # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() + trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() + assert trp_limit_after == TRP_LIMIT_AFTER + assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS + assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER + assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP + assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP @@ -303,9 +359,62 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] + assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) assert len(sdvt_module_after.items()) == 13 + # additional test for TRP ET factory behavior after the vote + trp_limit_test(stranger) + + +def trp_limit_test(stranger): + + easy_track = interface.EasyTrack(EASY_TRACK) + ldo_token = interface.ERC20(LDO_TOKEN) + to_spend = TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + max_spend_at_once = 5_000_000 * 10**18 + trp_committee_account = accounts.at(TRP_COMMITTEE, force=True) + + chain.snapshot() + + # check that there is no way to spend more then expected + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + [trp_committee_account], + [to_spend + 1], + stranger, + ) + + # spend all step by step + while to_spend > 0: + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + [trp_committee_account], + [min(max_spend_at_once, to_spend)], + stranger, + ) + to_spend -= min(max_spend_at_once, to_spend) + + # make sure there is nothing left so that you can't spend anymore + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + [trp_committee_account], + [1], + stranger, + ) + + chain.revert() # additional tests for SDVT module behavior after the vote # TODO fix test and enable diff --git a/utils/test/event_validators/allowed_recipients_registry.py b/utils/test/event_validators/allowed_recipients_registry.py index f418c38a3..4af93784b 100644 --- a/utils/test/event_validators/allowed_recipients_registry.py +++ b/utils/test/event_validators/allowed_recipients_registry.py @@ -4,7 +4,7 @@ def validate_set_limit_parameter_event( - event: EventDict, limit: int, period_duration_month: int, period_start_timestamp: int, emitted_by: str | None = None + event: EventDict, limit: int, period_duration_month: int, period_start_timestamp: int, emitted_by: str | None = None, is_dg_event: bool = False ): _events_chain = [ "LogScriptCall", @@ -13,6 +13,8 @@ def validate_set_limit_parameter_event( "LimitsParametersChanged", "ScriptResult", ] + if is_dg_event: + _events_chain += ["Executed"] validate_events_chain([e.name for e in event], _events_chain) @@ -60,10 +62,19 @@ def validate_update_spent_amount_event( def validate_set_spent_amount_event( event: EventDict, new_spent_amount: int, + emitted_by: str | None = None, + is_dg_event: bool = False ): _events_chain = ["LogScriptCall", "LogScriptCall", "SpentAmountChanged", "ScriptResult"] + if is_dg_event: + _events_chain += ["Executed"] validate_events_chain([e.name for e in event], _events_chain) assert event.count("SpentAmountChanged") == 1 assert event["SpentAmountChanged"]["_newSpentAmount"] == new_spent_amount + if emitted_by is not None: + event_emitted_by = convert.to_address(event["SpentAmountChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" From 198295516940cb75ef72da9c00a6d035306962e0 Mon Sep 17 00:00:00 2001 From: skhomuti Date: Tue, 21 Oct 2025 14:08:29 +0500 Subject: [PATCH 011/178] fix: update time retrieval method and enhance depositable node operator logic --- tests/regression/test_csm.py | 91 +++++++++++++++++++---------- utils/test/oracle_report_helpers.py | 3 +- 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/tests/regression/test_csm.py b/tests/regression/test_csm.py index a16bcf854..70f3d5686 100644 --- a/tests/regression/test_csm.py +++ b/tests/regression/test_csm.py @@ -88,27 +88,43 @@ def ejector(): def strikes(): return contracts.cs_strikes +@pytest.fixture(params=[pytest.param(1, id="1 key")]) +def keys_count(request): + return request.param @pytest.fixture -def depositable_node_operator(csm, accounting, permissionless_gate, stranger): +def depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger): + return _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger) + + +def _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger): increase_staking_module_share(module_id=CSM_MODULE_ID, share_multiplier=2) csm.cleanDepositQueue(2 * csm.getNonce(), {"from": stranger.address}) + keys_to_deposit = 0 for queue_priority in range(0, 6): deposit_batch = csm.depositQueueItem(queue_priority, csm.depositQueuePointers(queue_priority)["head"]) if deposit_batch: node_operator_id = (deposit_batch >> 192) & ((1 << 64) - 1) - keys_count = (deposit_batch >> 128) & ((1 << 64) - 1) - break + batch_keys_count = (deposit_batch >> 128) & ((1 << 64) - 1) + keys_to_deposit += batch_keys_count + if batch_keys_count >= keys_count: + break else: address = accounts[7].address - keys_count = 5 node_operator_id = csm_add_node_operator(csm, permissionless_gate, accounting, address, keys_count=keys_count) - return node_operator_id, keys_count + return node_operator_id, keys_to_deposit -@pytest.fixture -def node_operator(depositable_node_operator, csm, accounting) -> int: - node_operator, keys_count = depositable_node_operator +@pytest.fixture() +def node_operator(keys_count, csm, accounting, permissionless_gate, stranger) -> int: + total_node_operators = contracts.csm.getNodeOperatorsCount() + for no_id in range(0, total_node_operators): + no = csm.getNodeOperator(no_id) + if no["totalDepositedKeys"] - no["totalWithdrawnKeys"] >= keys_count: + return no_id + + # Fallback: use _depositable_node_operator + node_operator, keys_count = _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger) fill_deposit_buffer(keys_count) contracts.lido.deposit(keys_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) return node_operator @@ -195,13 +211,15 @@ def test_add_node_operator_permissionless(csm, permissionless_gate, accounting, @pytest.mark.usefixtures("pause_modules") def test_deposit(depositable_node_operator, csm, remove_stake_limit): - (node_operator, keys_count) = depositable_node_operator - fill_deposit_buffer(keys_count) + (node_operator, keys_to_deposit) = depositable_node_operator + no = csm.getNodeOperator(node_operator) + depositable_keys = no["depositableValidatorsCount"] - no["totalWithdrawnKeys"] + fill_deposit_buffer(keys_to_deposit) total_deposited_before = csm.getNodeOperator(node_operator)["totalDepositedKeys"] - contracts.lido.deposit(keys_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) + contracts.lido.deposit(keys_to_deposit, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) no = csm.getNodeOperator(node_operator) - assert no["totalDepositedKeys"] == total_deposited_before + keys_count + assert no["totalDepositedKeys"] == total_deposited_before + depositable_keys def test_mint_rewards_happy_path(csm, fee_distributor): @@ -229,7 +247,8 @@ def test_csm_target_limits(csm, node_operator): def test_csm_report_exited(csm, node_operator, extra_data_service): total_exited = csm.getStakingModuleSummary()["totalExitedValidators"] - exited_keys = 5 + no = csm.getNodeOperator(node_operator) + exited_keys = no["totalExitedKeys"] + 1 extra_data = extra_data_service.collect({(CSM_MODULE_ID, node_operator): exited_keys}, exited_keys, exited_keys) oracle_report( extraDataFormat=1, @@ -244,13 +263,14 @@ def test_csm_report_exited(csm, node_operator, extra_data_service): assert no["totalExitedKeys"] == exited_keys -def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_data_service, remove_stake_limit): +def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_data_service, remove_stake_limit, permissionless_gate, stranger): (exited_before, deposited_before, depositable_before) = contracts.staking_router.getStakingModuleSummary( CSM_MODULE_ID ) # Assure there are new exited keys - exited_keys = 5 + no = csm.getNodeOperator(node_operator) + exited_keys = no["totalExitedKeys"] + 1 extra_data = extra_data_service.collect({(CSM_MODULE_ID, node_operator): exited_keys}, exited_keys, exited_keys) oracle_report( extraDataFormat=1, @@ -262,21 +282,20 @@ def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_da ) # Assure there are new deposited keys - - deposits_count = 3 - new_keys = 5 - new_depositable = new_keys - deposits_count - csm_upload_keys(csm, accounting, node_operator, new_keys) - increase_staking_module_share(module_id=CSM_MODULE_ID, share_multiplier=2) + keys_to_deposit = 2 + depositable_no, deposits_count = _depositable_node_operator(keys_to_deposit, csm, accounting, permissionless_gate, stranger) fill_deposit_buffer(deposits_count) contracts.lido.deposit(deposits_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) + new_depositable = 5 + csm_upload_keys(csm, accounting, node_operator, new_depositable) + (exited_after, deposited_after, depositable_after) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) assert exited_after == exited_before + exited_keys assert deposited_after == deposited_before + deposits_count - assert depositable_after == depositable_before + new_depositable + assert depositable_after == depositable_before - deposits_count + new_depositable def test_csm_get_node_operator_summary(csm, node_operator, extra_data_service): @@ -309,7 +328,9 @@ def test_csm_get_node_operator_summary(csm, node_operator, extra_data_service): def test_csm_decrease_vetted_keys(csm, depositable_node_operator, stranger): - (node_operator, keys_count) = depositable_node_operator + (node_operator, _) = depositable_node_operator + no = csm.getNodeOperator(node_operator) + depositable_keys = no["depositableValidatorsCount"] - no["totalWithdrawnKeys"] total_added_keys = csm.getNodeOperator(node_operator)["totalAddedKeys"] block_number = web3.eth.get_block_number() block = web3.eth.get_block(block_number) @@ -320,7 +341,7 @@ def test_csm_decrease_vetted_keys(csm, depositable_node_operator, stranger): staking_module_id=CSM_MODULE_ID, nonce=staking_module_nonce, node_operator_ids=to_bytes(node_operator, 16), - vetted_signing_keys_counts=to_bytes(total_added_keys - keys_count, 32), + vetted_signing_keys_counts=to_bytes(total_added_keys - depositable_keys, 32), ) set_single_guardian(contracts.deposit_security_module, contracts.agent, stranger) @@ -328,7 +349,7 @@ def test_csm_decrease_vetted_keys(csm, depositable_node_operator, stranger): contracts.deposit_security_module.unvetSigningKeys(*unvet_args.to_tuple(), (0, 0), {"from": stranger.address}) no = csm.getNodeOperator(node_operator) - assert no["totalVettedKeys"] == total_added_keys - keys_count + assert no["totalVettedKeys"] == total_added_keys - depositable_keys def test_csm_penalize_node_operator(csm, accounting, node_operator, helpers): @@ -439,8 +460,10 @@ def test_csm_remove_key(csm, parameters_registry, accounting, node_operator): assert no["totalAddedKeys"] == keys_before - 1 -def test_eject_bad_performer(csm, accounting, ejector, strikes, node_operator, stranger): - index_to_eject = 0 +@pytest.mark.parametrize("keys_count", [pytest.param(2, id="2 keys")], indirect=True) +def test_eject_bad_performer(csm, accounting, ejector, strikes, node_operator, stranger, keys_count): + no = csm.getNodeOperator(node_operator) + index_to_eject = no["totalDepositedKeys"] - 2 pubkey_to_eject = csm.getSigningKeys(node_operator, index_to_eject, 1) leaf_to_eject = (node_operator, pubkey_to_eject, [1, 1, 1, 1, 1, 1]) another_pubkey = csm.getSigningKeys(node_operator, index_to_eject + 1, 1) @@ -484,18 +507,22 @@ def test_eject_bad_performer(csm, accounting, ejector, strikes, node_operator, s def test_voluntary_eject(csm, ejector, node_operator): eject_payment_value = get_sys_fee_to_eject() operator_address = csm.getNodeOperatorOwner(node_operator) + no = csm.getNodeOperator(node_operator) + index_to_eject = no["totalDepositedKeys"] - 1 tx = ejector.voluntaryEject( - node_operator, 0, 1, ZERO_ADDRESS, {"value": eject_payment_value, "from": operator_address} + node_operator, index_to_eject, 1, ZERO_ADDRESS, {"value": eject_payment_value, "from": operator_address} ) assert "TriggeredExitFeeRecorded" not in tx.events def test_report_validator_exit_delay(csm, accounting, parameters_registry, node_operator): - pubkey = csm.getSigningKeys(node_operator, 0, 1) day_in_seconds = 60 * 60 * 24 + no = csm.getNodeOperator(node_operator) + index_to_report = no["totalDepositedKeys"] - 1 + pubkey = csm.getSigningKeys(node_operator, index_to_report, 1) - tx = csm.reportValidatorExitDelay(node_operator, 0, pubkey, 7 * day_in_seconds, {"from": contracts.staking_router}) + tx = csm.reportValidatorExitDelay(node_operator, index_to_report, pubkey, 7 * day_in_seconds, {"from": contracts.staking_router}) assert "ValidatorExitDelayProcessed" in tx.events assert tx.events["ValidatorExitDelayProcessed"]["nodeOperatorId"] == node_operator assert tx.events["ValidatorExitDelayProcessed"]["pubkey"] == pubkey @@ -507,7 +534,9 @@ def test_report_validator_exit_delay(csm, accounting, parameters_registry, node_ def test_on_validator_exit_triggered(csm, node_operator): eject_payment_value = 1 - pubkey = csm.getSigningKeys(node_operator, 0, 1) + no = csm.getNodeOperator(node_operator) + index_to_report = no["totalDepositedKeys"] - 1 + pubkey = csm.getSigningKeys(node_operator, index_to_report, 1) exit_type = 3 tx = csm.onValidatorExitTriggered(node_operator, pubkey, 1, exit_type, {"from": contracts.staking_router}) diff --git a/utils/test/oracle_report_helpers.py b/utils/test/oracle_report_helpers.py index 6b4598493..ab3fa85d7 100644 --- a/utils/test/oracle_report_helpers.py +++ b/utils/test/oracle_report_helpers.py @@ -351,8 +351,7 @@ def wait_to_next_available_report_time(consensus_contract): else: raise - # Use chain.time() instead of block timestamp for consistency - time = chain.time() + time = web3.eth.get_block("latest").timestamp (_, EPOCHS_PER_FRAME, _) = consensus_contract.getFrameConfig() frame_start_with_offset = GENESIS_TIME + (refSlot + SLOTS_PER_EPOCH * EPOCHS_PER_FRAME + 1) * SECONDS_PER_SLOT chain.sleep(frame_start_with_offset - time) From 8c04845c84ec507bed5003f03f253df82a64dfdf Mon Sep 17 00:00:00 2001 From: F4ever Date: Tue, 21 Oct 2025 17:03:31 +0200 Subject: [PATCH 012/178] chore: fix ao extra data tests --- .../acceptance/test_accounting_oracle_negative.py | 3 ++- tests/regression/test_sanity_checks.py | 15 ++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/acceptance/test_accounting_oracle_negative.py b/tests/acceptance/test_accounting_oracle_negative.py index 2e65a9cbc..7e175e672 100644 --- a/tests/acceptance/test_accounting_oracle_negative.py +++ b/tests/acceptance/test_accounting_oracle_negative.py @@ -353,15 +353,16 @@ def test_unexpected_extra_data_item(self, extra_data_service: ExtraDataService) (1, 5): self.get_nor_operator_exited_keys(5) + 1, (1, 6): self.get_nor_operator_exited_keys(6) + 1, (1, 7): self.get_nor_operator_exited_keys(7) + 1, - (1, 8): self.get_nor_operator_exited_keys(8) + 1, (1, 9): self.get_nor_operator_exited_keys(9) + 1, (1, 10): self.get_nor_operator_exited_keys(10) + 1, + (1, 11): self.get_nor_operator_exited_keys(11) + 1, }, MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, 1, ) with reverts( + # In case of OUT_OF_RANGE error just remove exited NO (with all validators exited) from list above ^^^ encode_error( "UnexpectedExtraDataItemsCount(uint256,uint256)", [ diff --git a/tests/regression/test_sanity_checks.py b/tests/regression/test_sanity_checks.py index 3314d6c6a..4dca87fba 100644 --- a/tests/regression/test_sanity_checks.py +++ b/tests/regression/test_sanity_checks.py @@ -212,11 +212,16 @@ def test_accounting_oracle_too_much_extra_data(extra_data_service): nor_module_id = 1 nor_operators_count = contracts.node_operators_registry.getNodeOperatorsCount() i = 0 - while len(operators) < item_count and i < nor_operators_count: - (active, _, _, _, total_exited_validators_count, _, _) = contracts.node_operators_registry.getNodeOperator(i, True) - if(active): - operators[(nor_module_id, i)] = total_exited_validators_count + 1 - i = i + 1 + + for no_id in range(nor_operators_count): + (active, _, _, _, total_exited_validators_count, _, total_deposited_validators_count) = contracts.node_operators_registry.getNodeOperator(no_id, True) + + if active and total_exited_validators_count != total_deposited_validators_count: + operators[(nor_module_id, no_id)] = total_exited_validators_count + 1 + i += 1 + + if i == item_count: + break extra_data = extra_data_service.collect(operators, item_count, 1) From de081d46b63e0e749391334548e266443b7e960e Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 22 Oct 2025 07:05:58 +0000 Subject: [PATCH 013/178] fix: remove print --- utils/test/event_validators/payout.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/utils/test/event_validators/payout.py b/utils/test/event_validators/payout.py index a810c1386..e42ba274a 100644 --- a/utils/test/event_validators/payout.py +++ b/utils/test/event_validators/payout.py @@ -22,8 +22,6 @@ def validate_token_payout_event(event: EventDict, p: Payout, is_steth: bool = Fa _token_events_chain += ["VaultTransfer"] - print (event) - validate_events_chain([e.name for e in event], _token_events_chain) assert event.count("VaultTransfer") == 1 From 8faa3b3f7c5877fcf235bcbd10ecfe97cc3daf79 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 22 Oct 2025 07:24:51 +0000 Subject: [PATCH 014/178] fix: sdvt tests --- tests/test_2025_11_17.py | 41 +++++++++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/tests/test_2025_11_17.py b/tests/test_2025_11_17.py index 340fdc3fc..e139c99cc 100644 --- a/tests/test_2025_11_17.py +++ b/tests/test_2025_11_17.py @@ -1,6 +1,7 @@ from brownie import chain, interface, reverts, accounts from brownie.network.transaction import TransactionReceipt import pytest +import math from brownie.network.account import LocalAccount from utils.test.tx_tracing_helpers import ( @@ -15,6 +16,7 @@ from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id from utils.ipfs import get_lido_vote_cid_from_str +from utils.test.simple_dvt_helpers import simple_dvt_add_keys from utils.dual_governance import PROPOSAL_STATUS from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event from utils.allowed_recipients_registry import ( @@ -79,6 +81,8 @@ SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 SDVT_MODULE_NAME = "SimpleDVT" +NODE_OPERATORS_REGISTRY_ID = 1 +TOTAL_BASIS_POINTS = 10_000 MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 @@ -416,6 +420,35 @@ def trp_limit_test(stranger): chain.revert() +def execute_vote(): + if vote_ids_from_env: + vote_id = vote_ids_from_env[0] + if EXPECTED_VOTE_ID is not None: + assert vote_id == EXPECTED_VOTE_ID + elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: + vote_id = EXPECTED_VOTE_ID + else: + vote_id, _ = start_vote({"from": ldo_holder}, silent=True) + _, call_script_items = get_vote_items() + onchain_script = voting.getVote(vote_id)["script"] + assert onchain_script == encode_call_script(call_script_items) + is_executed = voting.getVote(vote_id)["executed"] + if not is_executed: + vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) + display_voting_events(vote_tx) + vote_events = group_voting_events_from_receipt(vote_tx) + if EXPECTED_DG_PROPOSAL_ID is not None: + assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() + if EXPECTED_DG_PROPOSAL_ID is not None: + details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) + if details["status"] != PROPOSAL_STATUS["executed"]: + if details["status"] == PROPOSAL_STATUS["submitted"]: + chain.sleep(timelock.getAfterSubmitDelay() + 1) + dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: + chain.sleep(timelock.getAfterScheduleDelay() + 1) + dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + # additional tests for SDVT module behavior after the vote # TODO fix test and enable def _test_stake_allocation_after_voting(accounts, helpers, ldo_holder, vote_ids_from_env): @@ -444,8 +477,7 @@ def _test_stake_allocation_after_voting(accounts, helpers, ldo_holder, vote_ids_ ) # share percentage should not change after second filling # VOTE! - vote_id = vote_ids_from_env[0] if vote_ids_from_env else start_vote({"from": ldo_holder}, silent=True)[0] - helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=VOTING, skip_time=3 * 60 * 60 * 24) + execute_vote() # add more keys to the module fill_sdvt_module_with_keys(evm_script_executor=evm_script_executor, total_keys=300, start_nop_id=last_nop_id - 1) @@ -487,8 +519,7 @@ def _test_sdvt_stake_allocation(accounts, helpers, ldo_holder, vote_ids_from_env sdvt_module_stats_before = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) # VOTE! - vote_id = vote_ids_from_env[0] if vote_ids_from_env else start_vote({"from": ldo_holder}, silent=True)[0] - helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=VOTING, skip_time=3 * 60 * 60 * 24) + execute_vote() # No new keys in the SDVT module lido.deposit(100, NODE_OPERATORS_REGISTRY_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) @@ -557,7 +588,7 @@ def get_staking_module_remaining_cap(staking_module_id: int) -> int: for module_summary in all_modules_summary ) - target_share = staking_router.getStakingModule(staking_module_id)["targetShare"] + target_share = staking_router.getStakingModule(staking_module_id)["stakeShareLimit"] return math.ceil((target_share * total_active_keys) / TOTAL_BASIS_POINTS) - module_active_keys From aa98877cdc7f7b50c69f067170ac3b3fe1b291ff Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 22 Oct 2025 08:11:51 +0000 Subject: [PATCH 015/178] fix: set up separate node for tests --- .github/workflows/normal_vote_ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/normal_vote_ci.yml b/.github/workflows/normal_vote_ci.yml index 650d57bb7..c5de583b2 100644 --- a/.github/workflows/normal_vote_ci.yml +++ b/.github/workflows/normal_vote_ci.yml @@ -15,6 +15,15 @@ jobs: runs-on: "ubuntu-latest" timeout-minutes: 100 + services: + hardhat-node: + image: ghcr.io/lidofinance/hardhat-node:2.26.0 + ports: + - 8545:8545 + env: + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} + HARDFORK: "cancun" + steps: - uses: actions/checkout@v4 - name: Main action @@ -30,6 +39,15 @@ jobs: runs-on: "ubuntu-latest" timeout-minutes: 100 + services: + hardhat-node: + image: ghcr.io/lidofinance/hardhat-node:2.26.0 + ports: + - 8545:8546 + env: + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} + HARDFORK: "cancun" + steps: - uses: actions/checkout@v4 - name: Main action From a43db452f9782f361a6383ad80c73b7b67809183 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 22 Oct 2025 08:16:31 +0000 Subject: [PATCH 016/178] fix: set up separate node for tests --- .github/workflows/normal_vote_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/normal_vote_ci.yml b/.github/workflows/normal_vote_ci.yml index c5de583b2..8afa07907 100644 --- a/.github/workflows/normal_vote_ci.yml +++ b/.github/workflows/normal_vote_ci.yml @@ -43,7 +43,7 @@ jobs: hardhat-node: image: ghcr.io/lidofinance/hardhat-node:2.26.0 ports: - - 8545:8546 + - 8546:8545 env: ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} HARDFORK: "cancun" From 232bbc776c753e618675717a2b608df910d90049 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 24 Oct 2025 10:37:07 +0000 Subject: [PATCH 017/178] fix: remove unused var --- scripts/vote_2025_11_17.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/vote_2025_11_17.py b/scripts/vote_2025_11_17.py index 6c5fea832..b41cbe2b6 100644 --- a/scripts/vote_2025_11_17.py +++ b/scripts/vote_2025_11_17.py @@ -47,8 +47,6 @@ SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 -NODE_OPERATORS_REGISTRY_ID = 1 - MATIC_FOR_TRANSFER = 508_106 * 10**18 From e603e51ded18a4221ca7bd9f5206b42d9755524a Mon Sep 17 00:00:00 2001 From: skhomuti Date: Fri, 24 Oct 2025 17:54:39 +0500 Subject: [PATCH 018/178] fix incorrect logic for counting depositable keys in csm tests --- tests/regression/test_csm.py | 39 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/regression/test_csm.py b/tests/regression/test_csm.py index 70f3d5686..84c36d03f 100644 --- a/tests/regression/test_csm.py +++ b/tests/regression/test_csm.py @@ -106,7 +106,6 @@ def _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, if deposit_batch: node_operator_id = (deposit_batch >> 192) & ((1 << 64) - 1) batch_keys_count = (deposit_batch >> 128) & ((1 << 64) - 1) - keys_to_deposit += batch_keys_count if batch_keys_count >= keys_count: break else: @@ -124,9 +123,10 @@ def node_operator(keys_count, csm, accounting, permissionless_gate, stranger) -> return no_id # Fallback: use _depositable_node_operator - node_operator, keys_count = _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger) - fill_deposit_buffer(keys_count) - contracts.lido.deposit(keys_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) + node_operator, required_deposits = _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger) + to_deposit = required_deposits + keys_count + fill_deposit_buffer(to_deposit) + contracts.lido.deposit(to_deposit, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) return node_operator @@ -210,16 +210,15 @@ def test_add_node_operator_permissionless(csm, permissionless_gate, accounting, @pytest.mark.usefixtures("pause_modules") -def test_deposit(depositable_node_operator, csm, remove_stake_limit): - (node_operator, keys_to_deposit) = depositable_node_operator - no = csm.getNodeOperator(node_operator) - depositable_keys = no["depositableValidatorsCount"] - no["totalWithdrawnKeys"] - fill_deposit_buffer(keys_to_deposit) +def test_deposit(depositable_node_operator, csm, remove_stake_limit, keys_count): + (node_operator, required_deposits) = depositable_node_operator + to_deposit = required_deposits + keys_count + fill_deposit_buffer(to_deposit) total_deposited_before = csm.getNodeOperator(node_operator)["totalDepositedKeys"] - contracts.lido.deposit(keys_to_deposit, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) + contracts.lido.deposit(to_deposit, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) no = csm.getNodeOperator(node_operator) - assert no["totalDepositedKeys"] == total_deposited_before + depositable_keys + assert no["totalDepositedKeys"] == total_deposited_before + keys_count def test_mint_rewards_happy_path(csm, fee_distributor): @@ -264,11 +263,9 @@ def test_csm_report_exited(csm, node_operator, extra_data_service): def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_data_service, remove_stake_limit, permissionless_gate, stranger): - (exited_before, deposited_before, depositable_before) = contracts.staking_router.getStakingModuleSummary( - CSM_MODULE_ID - ) # Assure there are new exited keys + (exited_before, _, _) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) no = csm.getNodeOperator(node_operator) exited_keys = no["totalExitedKeys"] + 1 extra_data = extra_data_service.collect({(CSM_MODULE_ID, node_operator): exited_keys}, exited_keys, exited_keys) @@ -283,19 +280,23 @@ def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_da # Assure there are new deposited keys keys_to_deposit = 2 - depositable_no, deposits_count = _depositable_node_operator(keys_to_deposit, csm, accounting, permissionless_gate, stranger) + depositable_no, required_deposits = _depositable_node_operator(keys_to_deposit, csm, accounting, permissionless_gate, stranger) + (_, deposited_before, _) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) + to_deposit = required_deposits + keys_to_deposit + fill_deposit_buffer(to_deposit) + contracts.lido.deposit(to_deposit, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) - fill_deposit_buffer(deposits_count) - contracts.lido.deposit(deposits_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) + # Assure there are new depositable keys + (_, _, depositable_before) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) new_depositable = 5 csm_upload_keys(csm, accounting, node_operator, new_depositable) (exited_after, deposited_after, depositable_after) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) assert exited_after == exited_before + exited_keys - assert deposited_after == deposited_before + deposits_count - assert depositable_after == depositable_before - deposits_count + new_depositable + assert deposited_after == deposited_before + to_deposit + assert depositable_after == depositable_before + new_depositable def test_csm_get_node_operator_summary(csm, node_operator, extra_data_service): From f0a197f23c762b81c68e7d0ead39c37258988114 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sun, 26 Oct 2025 14:53:39 +0000 Subject: [PATCH 019/178] fix: sdvt stakeShareLimit to 430 bps --- configs/config_mainnet.py | 2 +- scripts/vote_2025_11_17.py | 6 +++--- tests/test_2025_11_17.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 12420fed2..5f3a64b42 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -162,7 +162,7 @@ SIMPLE_DVT_ARAGON_APP_NAME = "simple-dvt" SIMPLE_DVT_ARAGON_APP_ID = "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4" SIMPLE_DVT_MODULE_STUCK_PENALTY_DELAY = 0 -SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 410 +SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 430 SIMPLE_DVT_MODULE_MODULE_FEE_BP = 800 SIMPLE_DVT_MODULE_TREASURY_FEE_BP = 200 SIMPLE_DVT_MODULE_ID = 2 diff --git a/scripts/vote_2025_11_17.py b/scripts/vote_2025_11_17.py index b41cbe2b6..5aa9593ef 100644 --- a/scripts/vote_2025_11_17.py +++ b/scripts/vote_2025_11_17.py @@ -7,7 +7,7 @@ 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX II. Increase SDVT target share -1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 === NON-DG ITEMS === III. Transfer MATIC from Lido Treasury to Lido Labs Foundation @@ -40,7 +40,7 @@ # ============================== Constants =================================== SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 410 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 SDVT_MODULE_MODULE_FEE_BP = 800 SDVT_MODULE_TREASURY_FEE_BP = 200 @@ -74,7 +74,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateStakingModule.encode_input( diff --git a/tests/test_2025_11_17.py b/tests/test_2025_11_17.py index e139c99cc..77048559e 100644 --- a/tests/test_2025_11_17.py +++ b/tests/test_2025_11_17.py @@ -74,7 +74,7 @@ SDVT_MODULE_ID = 2 SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 410 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 SDVT_MODULE_MODULE_FEE_BP = 800 SDVT_MODULE_TREASURY_FEE_BP = 200 @@ -265,7 +265,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP assert sdvt_module_before['id'] == SDVT_MODULE_ID @@ -341,7 +341,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 410 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP assert sdvt_module_after['id'] == SDVT_MODULE_ID From 11c71edcf5ee459ef2a88d8027b4628e64e849fb Mon Sep 17 00:00:00 2001 From: Nikita P Date: Mon, 27 Oct 2025 11:02:38 +0000 Subject: [PATCH 020/178] fix: 17 nov -> 24 nov --- scripts/{vote_2025_11_17.py => vote_2025_11_24.py} | 2 +- tests/{test_2025_11_17.py => test_2025_11_24.py} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename scripts/{vote_2025_11_17.py => vote_2025_11_24.py} (99%) rename tests/{test_2025_11_17.py => test_2025_11_24.py} (99%) diff --git a/scripts/vote_2025_11_17.py b/scripts/vote_2025_11_24.py similarity index 99% rename from scripts/vote_2025_11_17.py rename to scripts/vote_2025_11_24.py index 5aa9593ef..bdca4f5a8 100644 --- a/scripts/vote_2025_11_17.py +++ b/scripts/vote_2025_11_24.py @@ -1,5 +1,5 @@ """ -# Vote 2025_11_17 +# Vote 2025_11_24 === 1. DG PROPOPSAL === I. Decrease Easy Track TRP limit diff --git a/tests/test_2025_11_17.py b/tests/test_2025_11_24.py similarity index 99% rename from tests/test_2025_11_17.py rename to tests/test_2025_11_24.py index 77048559e..af417477b 100644 --- a/tests/test_2025_11_17.py +++ b/tests/test_2025_11_24.py @@ -38,7 +38,7 @@ # ============================================================================ # ============================== Import vote ================================= # ============================================================================ -from scripts.vote_2025_11_17 import start_vote, get_vote_items +from scripts.vote_2025_11_24 import start_vote, get_vote_items # ============================================================================ From 0c45f4a53499703e9acb61f10d3b6a8d97f95334 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 29 Oct 2025 17:19:24 +0000 Subject: [PATCH 021/178] fixes --- scripts/vote_2025_11_24.py | 18 ++-- tests/test_2025_11_24.py | 200 ++----------------------------------- 2 files changed, 17 insertions(+), 201 deletions(-) diff --git a/scripts/vote_2025_11_24.py b/scripts/vote_2025_11_24.py index bdca4f5a8..39aed91df 100644 --- a/scripts/vote_2025_11_24.py +++ b/scripts/vote_2025_11_24.py @@ -3,8 +3,8 @@ === 1. DG PROPOPSAL === I. Decrease Easy Track TRP limit -1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX -1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX +1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO +1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months II. Increase SDVT target share 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 @@ -47,6 +47,10 @@ SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +TRP_PERIOD_DURATION_MONTHS = 12 +TRP_NEW_LIMIT = 15_000_000 * 10**18 +TRP_NEW_SPENT_AMOUNT = 0 + MATIC_FOR_TRANSFER = 508_106 * 10**18 @@ -62,14 +66,14 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_items = [ agent_forward([ - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX - unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), + # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), ]), agent_forward([ - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months set_limit_parameters( - limit=11_000_000 * 10**18, - period_duration_months=12, + limit=TRP_NEW_LIMIT, + period_duration_months=TRP_PERIOD_DURATION_MONTHS, registry_address=ET_TRP_REGISTRY, ), ]), diff --git a/tests/test_2025_11_24.py b/tests/test_2025_11_24.py index af417477b..11f9b0c19 100644 --- a/tests/test_2025_11_24.py +++ b/tests/test_2025_11_24.py @@ -1,8 +1,6 @@ from brownie import chain, interface, reverts, accounts from brownie.network.transaction import TransactionReceipt import pytest -import math -from brownie.network.account import LocalAccount from utils.test.tx_tracing_helpers import ( group_voting_events_from_receipt, @@ -16,7 +14,6 @@ from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id from utils.ipfs import get_lido_vote_cid_from_str -from utils.test.simple_dvt_helpers import simple_dvt_add_keys from utils.dual_governance import PROPOSAL_STATUS from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event from utils.allowed_recipients_registry import ( @@ -28,7 +25,6 @@ validate_token_payout_event, Payout, ) -from utils.test.deposits_helpers import fill_deposit_buffer, drain_remained_buffered_ether from utils.test.event_validators.allowed_recipients_registry import ( validate_set_limit_parameter_event, validate_set_spent_amount_event, @@ -81,8 +77,6 @@ SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 SDVT_MODULE_NAME = "SimpleDVT" -NODE_OPERATORS_REGISTRY_ID = 1 -TOTAL_BASIS_POINTS = 10_000 MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 @@ -93,7 +87,7 @@ TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 TRP_ALREADY_SPENT_BEFORE = 2_676_801 * 10**18 TRP_ALREADY_SPENT_AFTER = 0 -TRP_LIMIT_AFTER = 11_000_000 * 10**18 +TRP_LIMIT_AFTER = 15_000_000 * 10**18 TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC TRP_PERIOD_DURATION_MONTHS = 12 @@ -112,7 +106,7 @@ def dual_governance_proposal_calls(): agent_forward([ set_limit_parameters( limit=TRP_LIMIT_AFTER, - period_duration_months=12, + period_duration_months=TRP_PERIOD_DURATION_MONTHS, registry_address=ET_TRP_REGISTRY, ), ]), @@ -254,8 +248,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # TODO add DG before proposal executed checks - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() assert trp_limit_before == TRP_LIMIT_BEFORE @@ -330,8 +324,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # TODO add DG after proposal executed checks - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to TODO XXX + # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() assert trp_limit_after == TRP_LIMIT_AFTER @@ -419,185 +413,3 @@ def trp_limit_test(stranger): ) chain.revert() - -def execute_vote(): - if vote_ids_from_env: - vote_id = vote_ids_from_env[0] - if EXPECTED_VOTE_ID is not None: - assert vote_id == EXPECTED_VOTE_ID - elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: - vote_id = EXPECTED_VOTE_ID - else: - vote_id, _ = start_vote({"from": ldo_holder}, silent=True) - _, call_script_items = get_vote_items() - onchain_script = voting.getVote(vote_id)["script"] - assert onchain_script == encode_call_script(call_script_items) - is_executed = voting.getVote(vote_id)["executed"] - if not is_executed: - vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) - display_voting_events(vote_tx) - vote_events = group_voting_events_from_receipt(vote_tx) - if EXPECTED_DG_PROPOSAL_ID is not None: - assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - if EXPECTED_DG_PROPOSAL_ID is not None: - details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) - if details["status"] != PROPOSAL_STATUS["executed"]: - if details["status"] == PROPOSAL_STATUS["submitted"]: - chain.sleep(timelock.getAfterSubmitDelay() + 1) - dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: - chain.sleep(timelock.getAfterScheduleDelay() + 1) - dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - -# additional tests for SDVT module behavior after the vote -# TODO fix test and enable -def _test_stake_allocation_after_voting(accounts, helpers, ldo_holder, vote_ids_from_env): - evm_script_executor: LocalAccount = accounts.at(ET_EVM_SCRIPT_EXECUTOR, force=True) - sdvt_remaining_cap_before: int = get_staking_module_remaining_cap(SDVT_MODULE_ID) - check_alloc_keys = sdvt_remaining_cap_before - - # Fill the module with keys. Keep last nop_id to add more keys to other node operators later - last_nop_id = fill_sdvt_module_with_keys( - evm_script_executor=evm_script_executor, total_keys=sdvt_remaining_cap_before - ) - - _, sdvt_allocation_percentage_after_filling = get_allocation_percentage(check_alloc_keys) - - assert sdvt_allocation_percentage_after_filling == 0.5 # 0.5% of total allocated keys — current target share - - # add more keys to the module to check that percentage wasn't changed - last_nop_id = fill_sdvt_module_with_keys( - evm_script_executor=evm_script_executor, total_keys=200, start_nop_id=last_nop_id - 1 - ) - - _, sdvt_allocation_percentage_after = get_allocation_percentage(check_alloc_keys + 200) - - assert ( - sdvt_allocation_percentage_after == sdvt_allocation_percentage_after_filling - ) # share percentage should not change after second filling - - # VOTE! - execute_vote() - - # add more keys to the module - fill_sdvt_module_with_keys(evm_script_executor=evm_script_executor, total_keys=300, start_nop_id=last_nop_id - 1) - - sdvt_remaining_cap_after_vote = get_staking_module_remaining_cap(SDVT_MODULE_ID) - - assert sdvt_remaining_cap_after_vote > sdvt_remaining_cap_before # remaining cap should increase after the vote - - _, sdvt_allocation_percentage_after_vote = get_allocation_percentage(sdvt_remaining_cap_after_vote) - - assert ( - sdvt_allocation_percentage_after_vote > sdvt_allocation_percentage_after_filling - ) # allocation percentage should increase after the vote - - -def fill_sdvt_module(): - staking_router = interface.StakingRouter(STAKING_ROUTER) - lido = interface.Lido(LIDO) - - sdvt_module_stats = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) - keys_to_allocate = sdvt_module_stats["depositableValidatorsCount"] - fill_deposit_buffer(keys_to_allocate) - lido.deposit(keys_to_allocate, SDVT_MODULE_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) - -# TODO fix test and enable -def _test_sdvt_stake_allocation(accounts, helpers, ldo_holder, vote_ids_from_env): - staking_router = interface.StakingRouter(STAKING_ROUTER) - lido = interface.Lido(LIDO) - - evm_script_executor: LocalAccount = accounts.at(ET_EVM_SCRIPT_EXECUTOR, force=True) - new_sdvt_keys_amount = 60 - - # prepare initial state - fill_sdvt_module() - drain_remained_buffered_ether() - fill_deposit_buffer(200) - - nor_module_stats_before = staking_router.getStakingModuleSummary(NODE_OPERATORS_REGISTRY_ID) - sdvt_module_stats_before = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) - - # VOTE! - execute_vote() - - # No new keys in the SDVT module - lido.deposit(100, NODE_OPERATORS_REGISTRY_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) - lido.deposit(100, SDVT_MODULE_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) - nor_module_stats_after_vote = staking_router.getStakingModuleSummary(NODE_OPERATORS_REGISTRY_ID) - sdvt_module_stats_after_vote = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) - - assert ( - sdvt_module_stats_after_vote["totalDepositedValidators"] - sdvt_module_stats_before["totalDepositedValidators"] - == 0 - ), "No new keys should go to the SDVT module" - assert ( - nor_module_stats_after_vote["totalDepositedValidators"] - nor_module_stats_before["totalDepositedValidators"] - == 100 - ), "All keys should go to the NOR module" - - # Add new keys to the SDVT module - fill_sdvt_module_with_keys(evm_script_executor=evm_script_executor, total_keys=new_sdvt_keys_amount) - - lido.deposit(100, SDVT_MODULE_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) - lido.deposit(100, NODE_OPERATORS_REGISTRY_ID, "0x0", {"from": DEPOSIT_SECURITY_MODULE}) - nor_module_stats_after = staking_router.getStakingModuleSummary(NODE_OPERATORS_REGISTRY_ID) - sdvt_module_stats_after = staking_router.getStakingModuleSummary(SDVT_MODULE_ID) - - assert sdvt_module_stats_after["depositableValidatorsCount"] == 0, "All accessible keys should be deposited" - assert ( - sdvt_module_stats_after["totalDepositedValidators"] - == sdvt_module_stats_after_vote["totalDepositedValidators"] + new_sdvt_keys_amount - ), f"{new_sdvt_keys_amount} keys should go to the SDVT module" - assert nor_module_stats_after["totalDepositedValidators"] == nor_module_stats_after_vote[ - "totalDepositedValidators" - ] + (100 - new_sdvt_keys_amount), "All other keys should go to the NOR module" - - -def fill_sdvt_module_with_keys(evm_script_executor: LocalAccount, total_keys: int, start_nop_id: int = 0) -> int: - simple_dvt = interface.SimpleDVT(SDVT) - - if start_nop_id == 0: - start_nop_id = simple_dvt.getNodeOperatorsCount() - 1 - nop_id = start_nop_id - - keys_to_allocate = ( - total_keys if total_keys < 100 else 100 - ) # keys to allocate to each node operator, base batch is 100 keys per operator - steps = 1 if keys_to_allocate == total_keys else (total_keys // keys_to_allocate) + 1 - for idx in range(steps): - nop_id = start_nop_id - idx - simple_dvt_add_keys(simple_dvt, nop_id, keys_to_allocate) - simple_dvt.setNodeOperatorStakingLimit(nop_id, keys_to_allocate, {"from": evm_script_executor}) - - return nop_id - - -def get_staking_module_remaining_cap(staking_module_id: int) -> int: - staking_router = interface.StakingRouter(STAKING_ROUTER) - - module_summary = staking_router.getStakingModuleSummary(staking_module_id) - module_active_keys = module_summary["totalDepositedValidators"] - module_summary["totalExitedValidators"] - - all_modules_summary = [ - staking_router.getStakingModuleSummary(module_id) - for module_id in [NODE_OPERATORS_REGISTRY_ID, SDVT_MODULE_ID] - ] - total_active_keys = sum( - module_summary["totalDepositedValidators"] - module_summary["totalExitedValidators"] - for module_summary in all_modules_summary - ) - - target_share = staking_router.getStakingModule(staking_module_id)["stakeShareLimit"] - - return math.ceil((target_share * total_active_keys) / TOTAL_BASIS_POINTS) - module_active_keys - - -def get_allocation_percentage(keys_to_allocate: int) -> (float, float): - staking_router = interface.StakingRouter(STAKING_ROUTER) - - allocation = staking_router.getDepositsAllocation(keys_to_allocate) - total_allocated = sum(allocation["allocations"]) - return (round((allocation["allocations"][0] / total_allocated) * TOTAL_BASIS_POINTS) / 100), ( - round((allocation["allocations"][1] / total_allocated) * TOTAL_BASIS_POINTS) / 100 - ) From 4c03be15a796d7b6b6049f3b8c10f85233f5c26d Mon Sep 17 00:00:00 2001 From: skhomuti Date: Thu, 30 Oct 2025 21:31:13 +0500 Subject: [PATCH 022/178] fix missed pause modules for receiving deposits to csm directly --- tests/regression/test_csm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/regression/test_csm.py b/tests/regression/test_csm.py index 84c36d03f..151c626b2 100644 --- a/tests/regression/test_csm.py +++ b/tests/regression/test_csm.py @@ -262,6 +262,7 @@ def test_csm_report_exited(csm, node_operator, extra_data_service): assert no["totalExitedKeys"] == exited_keys +@pytest.mark.usefixtures("pause_modules") def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_data_service, remove_stake_limit, permissionless_gate, stranger): # Assure there are new exited keys From d3d62db00088ff2ebc12ba96089f412a3d77987d Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 30 Oct 2025 19:56:52 +0300 Subject: [PATCH 023/178] chore: init v3 vote files --- scripts/vote_2025_11_24_v3.py | 108 +++++++++++++++++++++ tests/_test_2025_11_24_v3.py | 175 ++++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 scripts/vote_2025_11_24_v3.py create mode 100644 tests/_test_2025_11_24_v3.py diff --git a/scripts/vote_2025_11_24_v3.py b/scripts/vote_2025_11_24_v3.py new file mode 100644 index 000000000..a3047aa7e --- /dev/null +++ b/scripts/vote_2025_11_24_v3.py @@ -0,0 +1,108 @@ +""" +# TODO Vote 2025__
+ +# TODO + +# TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. +""" + +from typing import Dict, List, Tuple + +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 get_deployer_account, get_is_live, get_priority_fee +from utils.mainnet_fork import pass_and_exec_dao_vote +from utils.dual_governance import submit_proposals + + +# ============================== Addresses =================================== +# TODO + + +# ============================= Description ================================== +# TODO +IPFS_DESCRIPTION = "" + + +# ================================ Main ====================================== +def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: + + + # TODO in case of using smart-contract based omnibus, retrieve vote items from omnibus contract + # voting_items = brownie.interface.SmartContractOmnibus(omnibus_contract).getVoteItems() + # 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())) + + + # TODO in case of using script based omnibus, write vote items manually + # dg_items = [ + # # TODO 1.1. DG voting item 1 description + # agent_forward([ + # (dg_item_address_1, dg_item_encoded_input_1) + # ]), + # # TODO 1.2. DG voting item 2 description + # agent_forward([ + # (dg_item_address_2, dg_item_encoded_input_2) + # ]), + # ] + # + # dg_call_script = submit_proposals([ + # (dg_items, "TODO DG proposal description") + # ]) + # + # vote_desc_items, call_script_items = zip( + # ( + # "TODO 1. DG submission description", + # dg_call_script[0] + # ), + # ( + # "TODO 2. Voting item 2 description", + # calldata_2, + # ), + # ( + # "TODO 3. Voting item 3 description", + # calldata_3, + # ), + # ) + + + # TODO return vote_desc_items, call_script_items + pass + + +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)) + + desc_ipfs = ( + calculate_vote_ipfs_description(IPFS_DESCRIPTION) + if silent else upload_vote_ipfs_description(IPFS_DESCRIPTION) + ) + + vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list( + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) + ) + + return vote_id, tx + + +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}.") + + +def start_and_execute_vote_on_fork_manual(): + 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) + print(f"Vote created: {vote_id}.") + pass_and_exec_dao_vote(int(vote_id), step_by_step=True) diff --git a/tests/_test_2025_11_24_v3.py b/tests/_test_2025_11_24_v3.py new file mode 100644 index 000000000..7c01f66ed --- /dev/null +++ b/tests/_test_2025_11_24_v3.py @@ -0,0 +1,175 @@ +from brownie import chain, interface +from brownie.network.transaction import TransactionReceipt +import pytest + +from utils.test.tx_tracing_helpers import ( + group_voting_events_from_receipt, + group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events +) +from utils.evm_script import encode_call_script +from utils.voting import find_metadata_by_vote_id +from utils.ipfs import get_lido_vote_cid_from_str +from utils.dual_governance import PROPOSAL_STATUS +from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event + + +# ============================================================================ +# ============================== Import vote ================================= +# ============================================================================ +# TODO import voting script +# from scripts.vote_* import start_vote, get_vote_items + + +# ============================================================================ +# ============================== Constants =================================== +# ============================================================================ +# TODO list all contract addresses used in tests - do not use imports from config! +# NOTE: these addresses might have a different value on other chains + +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" +EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" +DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" + +# TODO Set variable to None if item is not presented +EXPECTED_VOTE_ID = 1 +EXPECTED_DG_PROPOSAL_ID = 1 +EXPECTED_VOTE_EVENTS_COUNT = 1 +EXPECTED_DG_EVENTS_COUNT = 1 +IPFS_DESCRIPTION_HASH = "" + + +@pytest.fixture(scope="module") +def dual_governance_proposal_calls(): + # TODO Create all the dual governance calls that match the voting script + dg_items = [ + # # TODO 1.1. DG voting item 1 description + # agent_forward([ + # (dg_item_address_1, dg_item_encoded_input_1) + # ]), + # # TODO 1.2. DG voting item 2 description + # agent_forward([ + # (dg_item_address_2, dg_item_encoded_input_2) + # ]), + ] + + # Convert each dg_item to the expected format + proposal_calls = [] + for dg_item in dg_items: + target, data = dg_item # agent_forward returns (target, data) + proposal_calls.append({ + "target": target, + "value": 0, + "data": data + }) + + return proposal_calls + + +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + voting = interface.Voting(VOTING) + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + + + # ========================================================================= + # ======================== Identify or Create vote ======================== + # ========================================================================= + if vote_ids_from_env: + vote_id = vote_ids_from_env[0] + if EXPECTED_VOTE_ID is not None: + assert vote_id == EXPECTED_VOTE_ID + elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: + vote_id = EXPECTED_VOTE_ID + else: + vote_id, _ = start_vote({"from": ldo_holder}, silent=True) + + _, call_script_items = get_vote_items() + onchain_script = voting.getVote(vote_id)["script"] + assert onchain_script == encode_call_script(call_script_items) + + + # ========================================================================= + # ============================= Execute Vote ============================== + # ========================================================================= + is_executed = voting.getVote(vote_id)["executed"] + if not is_executed: + # ======================================================================= + # ========================= Before voting checks ======================== + # ======================================================================= + # TODO add before voting checks + + + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH + + vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) + display_voting_events(vote_tx) + vote_events = group_voting_events_from_receipt(vote_tx) + + + # ======================================================================= + # ========================= After voting checks ========================= + # ======================================================================= + # TODO add after voting tests + + + assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT + assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT + if EXPECTED_DG_PROPOSAL_ID is not None: + assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() + + # TODO Validate DG Proposal Submit event + # validate_dual_governance_submit_event( + # vote_events[0], + # proposal_id=EXPECTED_DG_PROPOSAL_ID, + # proposer=VOTING, + # executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + # metadata="TODO DG proposal description", + # proposal_calls=dual_governance_proposal_calls, + # emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], + # ) + + # TODO validate all other voting events + + + if EXPECTED_DG_PROPOSAL_ID is not None: + details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) + if details["status"] != PROPOSAL_STATUS["executed"]: + # ========================================================================= + # ================== DG before proposal executed checks =================== + # ========================================================================= + # TODO add DG before proposal executed checks + + + if details["status"] == PROPOSAL_STATUS["submitted"]: + chain.sleep(timelock.getAfterSubmitDelay() + 1) + dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + + if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: + chain.sleep(timelock.getAfterScheduleDelay() + 1) + dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + display_dg_events(dg_tx) + dg_events = group_dg_events_from_receipt( + dg_tx, + timelock=EMERGENCY_PROTECTED_TIMELOCK, + admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + ) + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + + # TODO validate all DG events + + + # ========================================================================= + # ==================== After DG proposal executed checks ================== + # ========================================================================= + # TODO add DG after proposal executed checks From 3942d63865a8710f3ee01263668ef07753d8a4e6 Mon Sep 17 00:00:00 2001 From: dry914 Date: Fri, 31 Oct 2025 17:02:34 +0300 Subject: [PATCH 024/178] feat: add draft voting script --- interfaces/V3LaunchOmnibus.json | 143 ++++++++++++++++++ ... => vote_2025_11_24_mainnet_v3_upgrade.py} | 92 ++++++----- ... => test_2025_11_24_mainnet_v3_upgrade.py} | 0 3 files changed, 187 insertions(+), 48 deletions(-) create mode 100644 interfaces/V3LaunchOmnibus.json rename scripts/{vote_2025_11_24_v3.py => vote_2025_11_24_mainnet_v3_upgrade.py} (52%) rename tests/{_test_2025_11_24_v3.py => test_2025_11_24_mainnet_v3_upgrade.py} (100%) diff --git a/interfaces/V3LaunchOmnibus.json b/interfaces/V3LaunchOmnibus.json new file mode 100644 index 000000000..6f6fcb07a --- /dev/null +++ b/interfaces/V3LaunchOmnibus.json @@ -0,0 +1,143 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "proposalMetadata", + "type": "string" + } + ], + "name": "getEVMScript", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "internalType": "string", + "name": "proposalMetadata", + "type": "string" + } + ], + "name": "getNewVoteCallBytecode", + "outputs": [ + { + "internalType": "bytes", + "name": "newVoteBytecode", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVoteItems", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OmnibusBase.ScriptCall", + "name": "call", + "type": "tuple" + } + ], + "internalType": "struct OmnibusBase.VoteItem[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVotingVoteItems", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OmnibusBase.ScriptCall", + "name": "call", + "type": "tuple" + } + ], + "internalType": "struct OmnibusBase.VoteItem[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "voteId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "proposalMetadata", + "type": "string" + } + ], + "name": "isValidVoteScript", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/scripts/vote_2025_11_24_v3.py b/scripts/vote_2025_11_24_mainnet_v3_upgrade.py similarity index 52% rename from scripts/vote_2025_11_24_v3.py rename to scripts/vote_2025_11_24_mainnet_v3_upgrade.py index a3047aa7e..37132af80 100644 --- a/scripts/vote_2025_11_24_v3.py +++ b/scripts/vote_2025_11_24_mainnet_v3_upgrade.py @@ -1,7 +1,26 @@ """ -# TODO Vote 2025__
- -# TODO +Vote 2025_11_24 - Mainnet V3 Upgrade + +=== 1. DG PROPOSAL === +I. Lido V3 Upgrade +1.1. Call UpgradeTemplateV3.startUpgrade +1.2. Upgrade LidoLocator implementation +1.3. Grant APP_MANAGER_ROLE to the AGENT +1.4. Set Lido implementation in Kernel +1.5. Revoke APP_MANAGER_ROLE from the AGENT on Kernel +1.6. Revoke REQUEST_BURN_SHARES_ROLE from Lido +1.7. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking modules (NodeOperatorsRegistry) +1.8. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT +1.9. Revoke REQUEST_BURN_SHARES_ROLE from CS Accounting +1.10. Upgrade AccountingOracle implementation +1.11. Revoke REPORT_REWARDS_MINTED_ROLE from Lido +1.12. Grant REPORT_REWARDS_MINTED_ROLE to Accounting +1.13. Call UpgradeTemplateV3.finishUpgrade +1.14. Revoke REQUEST_BURN_SHARES_ROLE from Hoodi Sandbox module (only on Hoodi) + +=== NON-DG ITEMS === +II. Easy Track factories for Lido V3 +2. Add Lido V3 factories to Easy Track registry # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ @@ -13,64 +32,41 @@ from utils.config import get_deployer_account, get_is_live, get_priority_fee from utils.mainnet_fork import pass_and_exec_dao_vote from utils.dual_governance import submit_proposals +from brownie import interface # ============================== Addresses =================================== -# TODO +omnibus_contract = "0x67988077f29FbA661911d9567E05cc52C51ca1B0" # TODO replace with the actual omnibus contract address # ============================= Description ================================== # TODO -IPFS_DESCRIPTION = "" +IPFS_DESCRIPTION = "TODO description for IPFS" # ================================ Main ====================================== def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: + vote_desc_items = [] + call_script_items = [] + + # receive DG vote items from omnibus contract + dg_items = interface.V3LaunchOmnibus(omnibus_contract).getVoteItems() + + dg_call_script = submit_proposals([ + (dg_items, "TODO DG proposal description") + ]) + + vote_desc_items.append("TODO DG submission description") + call_script_items.append(dg_call_script) + + # receive non-DG vote items from omnibus contract + voting_items = interface.V3LaunchOmnibus(omnibus_contract).getVotingVoteItems() + for desc, call_script in voting_items: + vote_desc_items.append(desc) + call_script_items.append((call_script[0], call_script[1].hex())) - # TODO in case of using smart-contract based omnibus, retrieve vote items from omnibus contract - # voting_items = brownie.interface.SmartContractOmnibus(omnibus_contract).getVoteItems() - # 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())) - - - # TODO in case of using script based omnibus, write vote items manually - # dg_items = [ - # # TODO 1.1. DG voting item 1 description - # agent_forward([ - # (dg_item_address_1, dg_item_encoded_input_1) - # ]), - # # TODO 1.2. DG voting item 2 description - # agent_forward([ - # (dg_item_address_2, dg_item_encoded_input_2) - # ]), - # ] - # - # dg_call_script = submit_proposals([ - # (dg_items, "TODO DG proposal description") - # ]) - # - # vote_desc_items, call_script_items = zip( - # ( - # "TODO 1. DG submission description", - # dg_call_script[0] - # ), - # ( - # "TODO 2. Voting item 2 description", - # calldata_2, - # ), - # ( - # "TODO 3. Voting item 3 description", - # calldata_3, - # ), - # ) - - - # TODO return vote_desc_items, call_script_items - pass + return vote_desc_items, call_script_items def start_vote(tx_params: Dict[str, str], silent: bool = False): diff --git a/tests/_test_2025_11_24_v3.py b/tests/test_2025_11_24_mainnet_v3_upgrade.py similarity index 100% rename from tests/_test_2025_11_24_v3.py rename to tests/test_2025_11_24_mainnet_v3_upgrade.py From 900b3e13e3adb1a74c941797fa4f707886caa986 Mon Sep 17 00:00:00 2001 From: dry914 Date: Fri, 31 Oct 2025 18:55:50 +0300 Subject: [PATCH 025/178] chore: rename script --- ...4_mainnet_v3_upgrade.py => upgrade_2025_11_24_mainnet_v3.py} | 2 ++ ...4_mainnet_v3_upgrade.py => upgrade_2025_11_24_mainnet_v3.py} | 0 2 files changed, 2 insertions(+) rename scripts/{vote_2025_11_24_mainnet_v3_upgrade.py => upgrade_2025_11_24_mainnet_v3.py} (97%) rename tests/{test_2025_11_24_mainnet_v3_upgrade.py => upgrade_2025_11_24_mainnet_v3.py} (100%) diff --git a/scripts/vote_2025_11_24_mainnet_v3_upgrade.py b/scripts/upgrade_2025_11_24_mainnet_v3.py similarity index 97% rename from scripts/vote_2025_11_24_mainnet_v3_upgrade.py rename to scripts/upgrade_2025_11_24_mainnet_v3.py index 37132af80..99b6c24c9 100644 --- a/scripts/vote_2025_11_24_mainnet_v3_upgrade.py +++ b/scripts/upgrade_2025_11_24_mainnet_v3.py @@ -82,6 +82,8 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) ) + assert interface.V3LaunchOmnibus(omnibus_contract).isValidVoteScript(vote_id) + return vote_id, tx diff --git a/tests/test_2025_11_24_mainnet_v3_upgrade.py b/tests/upgrade_2025_11_24_mainnet_v3.py similarity index 100% rename from tests/test_2025_11_24_mainnet_v3_upgrade.py rename to tests/upgrade_2025_11_24_mainnet_v3.py From 07dc92eb5247cd88a054c326f9525649ffd1c070 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 6 Nov 2025 09:44:36 +0000 Subject: [PATCH 026/178] fix: matic recipient --- scripts/vote_2025_11_24.py | 12 ++++++------ tests/test_2025_11_24.py | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/vote_2025_11_24.py b/scripts/vote_2025_11_24.py index 39aed91df..af4f89197 100644 --- a/scripts/vote_2025_11_24.py +++ b/scripts/vote_2025_11_24.py @@ -10,8 +10,8 @@ 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 === NON-DG ITEMS === -III. Transfer MATIC from Lido Treasury to Lido Labs Foundation -2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F +III. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig +2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ @@ -35,7 +35,7 @@ # ============================== Addresses =================================== ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -LIDO_LABS_MS = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" +LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" # ============================== Constants =================================== @@ -104,11 +104,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_call_script[0] ), ( - "2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F", + "2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5", make_matic_payout( - target_address=LIDO_LABS_MS, + target_address=LOL_MS, matic_in_wei=MATIC_FOR_TRANSFER, - reference="Transfer 508,106 MATIC from Treasury to Lido Labs Foundation multisig", + reference="Transfer 508,106 MATIC from Treasury to Liquidity Observation Lab (LOL) Multisig", ), ) ) diff --git a/tests/test_2025_11_24.py b/tests/test_2025_11_24.py index 11f9b0c19..930dc2c29 100644 --- a/tests/test_2025_11_24.py +++ b/tests/test_2025_11_24.py @@ -50,7 +50,7 @@ DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -LIDO_LABS_MS = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" +LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" DEPOSIT_SECURITY_MODULE = "0xffa96d84def2ea035c7ab153d8b991128e3d72fd" @@ -181,10 +181,10 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # TODO add before voting checks - # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F + # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 matic_treasury_balance_before = matic_token.balanceOf(agent.address) assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE - matic_labs_balance_before = matic_token.balanceOf(LIDO_LABS_MS) + matic_labs_balance_before = matic_token.balanceOf(LOL_MS) assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE @@ -200,14 +200,14 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # TODO add after voting tests - # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F + # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 matic_treasury_balance_after = matic_token.balanceOf(agent.address) assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER - matic_labs_balance_after = matic_token.balanceOf(LIDO_LABS_MS) + matic_labs_balance_after = matic_token.balanceOf(LOL_MS) assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER - # make sure Lido Labs can actually spend the received MATIC - matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LIDO_LABS_MS}) - assert matic_token.balanceOf(LIDO_LABS_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 + # make sure LOL can actually spend the received MATIC + matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) + assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT @@ -233,7 +233,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g p=Payout( token_addr=MATIC_TOKEN, from_addr=AGENT, - to_addr=LIDO_LABS_MS, + to_addr=LOL_MS, amount=MATIC_IN_LIDO_LABS_AFTER), is_steth=False, emitted_by=AGENT From 1af6bca4ea47e21c789ea9420a6f8be2ea9df2b8 Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 6 Nov 2025 22:00:30 +0300 Subject: [PATCH 027/178] feat: single voting script --- scripts/upgrade_2025_11_24_mainnet_v3.py | 74 ++++-- scripts/vote_2025_11_24.py | 151 ----------- ...11_24.py => test_2025_11_24_mainnet_v3.py} | 239 +++++++++--------- tests/upgrade_2025_11_24_mainnet_v3.py | 175 ------------- 4 files changed, 166 insertions(+), 473 deletions(-) delete mode 100644 scripts/vote_2025_11_24.py rename tests/{test_2025_11_24.py => test_2025_11_24_mainnet_v3.py} (65%) delete mode 100644 tests/upgrade_2025_11_24_mainnet_v3.py diff --git a/scripts/upgrade_2025_11_24_mainnet_v3.py b/scripts/upgrade_2025_11_24_mainnet_v3.py index 99b6c24c9..28f3120fa 100644 --- a/scripts/upgrade_2025_11_24_mainnet_v3.py +++ b/scripts/upgrade_2025_11_24_mainnet_v3.py @@ -1,47 +1,63 @@ """ -Vote 2025_11_24 - Mainnet V3 Upgrade - -=== 1. DG PROPOSAL === -I. Lido V3 Upgrade -1.1. Call UpgradeTemplateV3.startUpgrade -1.2. Upgrade LidoLocator implementation -1.3. Grant APP_MANAGER_ROLE to the AGENT -1.4. Set Lido implementation in Kernel -1.5. Revoke APP_MANAGER_ROLE from the AGENT on Kernel -1.6. Revoke REQUEST_BURN_SHARES_ROLE from Lido -1.7. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking modules (NodeOperatorsRegistry) -1.8. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT -1.9. Revoke REQUEST_BURN_SHARES_ROLE from CS Accounting -1.10. Upgrade AccountingOracle implementation -1.11. Revoke REPORT_REWARDS_MINTED_ROLE from Lido -1.12. Grant REPORT_REWARDS_MINTED_ROLE to Accounting -1.13. Call UpgradeTemplateV3.finishUpgrade -1.14. Revoke REQUEST_BURN_SHARES_ROLE from Hoodi Sandbox module (only on Hoodi) +# Vote 2025_11_24 + +=== 1. DG PROPOPSAL === +I. Decrease Easy Track TRP limit +1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO +1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + +II. Increase SDVT target share +1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 === NON-DG ITEMS === -II. Easy Track factories for Lido V3 -2. Add Lido V3 factories to Easy Track registry +III. Transfer MATIC from Lido Treasury to Lido Labs Foundation +2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ from typing import Dict, List, Tuple +from utils.finance import make_matic_payout 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 get_deployer_account, get_is_live, get_priority_fee from utils.mainnet_fork import pass_and_exec_dao_vote from utils.dual_governance import submit_proposals +from utils.agent import agent_forward from brownie import interface +from utils.allowed_recipients_registry import ( + unsafe_set_spent_amount, + set_limit_parameters, +) # ============================== Addresses =================================== -omnibus_contract = "0x67988077f29FbA661911d9567E05cc52C51ca1B0" # TODO replace with the actual omnibus contract address +ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +LIDO_LABS_MS = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" + +OMNIBUS_CONTRACT = "0xA3710716965497e62bC3165Eb7DD2a1B1437f8Af" # TODO replace with the actual omnibus contract address + +# ============================== Constants =================================== +SDVT_MODULE_ID = 2 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 +SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_MODULE_FEE_BP = 800 +SDVT_MODULE_TREASURY_FEE_BP = 200 +SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + +TRP_PERIOD_DURATION_MONTHS = 12 +TRP_NEW_LIMIT = 15_000_000 * 10**18 +TRP_NEW_SPENT_AMOUNT = 0 + +MATIC_FOR_TRANSFER = 508_106 * 10**18 # ============================= Description ================================== # TODO -IPFS_DESCRIPTION = "TODO description for IPFS" +IPFS_DESCRIPTION = "omni nov 2025" # ================================ Main ====================================== @@ -50,17 +66,21 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: call_script_items = [] # receive DG vote items from omnibus contract - dg_items = interface.V3LaunchOmnibus(omnibus_contract).getVoteItems() + contract_dg_items = interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).getVoteItems() + + dg_items = [] + for desc, call_script in contract_dg_items: # TODO looks like this descriptions are not used + dg_items.append((call_script[0], '0x' + call_script[1].hex())) dg_call_script = submit_proposals([ - (dg_items, "TODO DG proposal description") + (dg_items, "TODO DG proposal description") # TODO looks like this description is not used ]) vote_desc_items.append("TODO DG submission description") - call_script_items.append(dg_call_script) + call_script_items.append(dg_call_script[0]) # receive non-DG vote items from omnibus contract - voting_items = interface.V3LaunchOmnibus(omnibus_contract).getVotingVoteItems() + voting_items = interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).getVotingVoteItems() for desc, call_script in voting_items: vote_desc_items.append(desc) @@ -82,7 +102,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) ) - assert interface.V3LaunchOmnibus(omnibus_contract).isValidVoteScript(vote_id) + # TODO assert interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).isValidVoteScript(vote_id, ) return vote_id, tx diff --git a/scripts/vote_2025_11_24.py b/scripts/vote_2025_11_24.py deleted file mode 100644 index 39aed91df..000000000 --- a/scripts/vote_2025_11_24.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -# Vote 2025_11_24 - -=== 1. DG PROPOPSAL === -I. Decrease Easy Track TRP limit -1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO -1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - -II. Increase SDVT target share -1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - -=== NON-DG ITEMS === -III. Transfer MATIC from Lido Treasury to Lido Labs Foundation -2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F - -# TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. -""" - -from typing import Dict, List, Tuple - -from utils.finance import make_matic_payout -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 get_deployer_account, get_is_live, get_priority_fee -from utils.mainnet_fork import pass_and_exec_dao_vote -from utils.dual_governance import submit_proposals -from utils.agent import agent_forward -from brownie import interface -from utils.allowed_recipients_registry import ( - unsafe_set_spent_amount, - set_limit_parameters, -) - - -# ============================== Addresses =================================== -ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" -STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -LIDO_LABS_MS = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" - - -# ============================== Constants =================================== -SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 -SDVT_MODULE_MODULE_FEE_BP = 800 -SDVT_MODULE_TREASURY_FEE_BP = 200 -SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 -SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 - -TRP_PERIOD_DURATION_MONTHS = 12 -TRP_NEW_LIMIT = 15_000_000 * 10**18 -TRP_NEW_SPENT_AMOUNT = 0 - -MATIC_FOR_TRANSFER = 508_106 * 10**18 - - -# ============================= Description ================================== -# TODO -IPFS_DESCRIPTION = "omni nov 2025" - - -# ================================ Main ====================================== -def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: - - staking_router = interface.StakingRouter(STAKING_ROUTER) - - dg_items = [ - agent_forward([ - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), - ]), - agent_forward([ - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - set_limit_parameters( - limit=TRP_NEW_LIMIT, - period_duration_months=TRP_PERIOD_DURATION_MONTHS, - registry_address=ET_TRP_REGISTRY, - ), - ]), - agent_forward([ - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - ( - staking_router.address, - staking_router.updateStakingModule.encode_input( - SDVT_MODULE_ID, - SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, - SDVT_MODULE_MODULE_FEE_BP, - SDVT_MODULE_TREASURY_FEE_BP, - SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, - SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, - ), - ), - ]) - ] - - dg_call_script = submit_proposals([ - (dg_items, "TODO DG proposal description") - ]) - - vote_desc_items, call_script_items = zip( - ( - "TODO 1. DG submission description", - dg_call_script[0] - ), - ( - "2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F", - make_matic_payout( - target_address=LIDO_LABS_MS, - matic_in_wei=MATIC_FOR_TRANSFER, - reference="Transfer 508,106 MATIC from Treasury to Lido Labs Foundation multisig", - ), - ) - ) - - 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)) - - desc_ipfs = ( - calculate_vote_ipfs_description(IPFS_DESCRIPTION) - if silent else upload_vote_ipfs_description(IPFS_DESCRIPTION) - ) - - vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list( - create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) - ) - - return vote_id, tx - - -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}.") - - -def start_and_execute_vote_on_fork_manual(): - 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) - print(f"Vote created: {vote_id}.") - pass_and_exec_dao_vote(int(vote_id), step_by_step=True) diff --git a/tests/test_2025_11_24.py b/tests/test_2025_11_24_mainnet_v3.py similarity index 65% rename from tests/test_2025_11_24.py rename to tests/test_2025_11_24_mainnet_v3.py index 11f9b0c19..7a5851a15 100644 --- a/tests/test_2025_11_24.py +++ b/tests/test_2025_11_24_mainnet_v3.py @@ -34,7 +34,7 @@ # ============================================================================ # ============================== Import vote ================================= # ============================================================================ -from scripts.vote_2025_11_24 import start_vote, get_vote_items +from scripts.upgrade_2025_11_24_mainnet_v3 import start_vote, get_vote_items # ============================================================================ @@ -125,8 +125,7 @@ def dual_governance_proposal_calls(): ), ]) ] - - + # Convert each dg_item to the expected format proposal_calls = [] for dg_item in dg_items: @@ -200,44 +199,44 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # TODO add after voting tests - # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F - matic_treasury_balance_after = matic_token.balanceOf(agent.address) - assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER - matic_labs_balance_after = matic_token.balanceOf(LIDO_LABS_MS) - assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER - # make sure Lido Labs can actually spend the received MATIC - matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LIDO_LABS_MS}) - assert matic_token.balanceOf(LIDO_LABS_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 - assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 - - assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT - assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT - if EXPECTED_DG_PROPOSAL_ID is not None: - assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - - # Validate DG Proposal Submit event - validate_dual_governance_submit_event( - vote_events[0], - proposal_id=EXPECTED_DG_PROPOSAL_ID, - proposer=VOTING, - executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="TODO DG proposal description", - proposal_calls=dual_governance_proposal_calls, - emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], - ) - - # TODO validate all other voting events - - validate_token_payout_event( - event=vote_events[1], - p=Payout( - token_addr=MATIC_TOKEN, - from_addr=AGENT, - to_addr=LIDO_LABS_MS, - amount=MATIC_IN_LIDO_LABS_AFTER), - is_steth=False, - emitted_by=AGENT - ) + # # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F + # matic_treasury_balance_after = matic_token.balanceOf(agent.address) + # assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER + # matic_labs_balance_after = matic_token.balanceOf(LIDO_LABS_MS) + # assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER + # # make sure Lido Labs can actually spend the received MATIC + # matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LIDO_LABS_MS}) + # assert matic_token.balanceOf(LIDO_LABS_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 + # assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 + + # assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT + # assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT + # if EXPECTED_DG_PROPOSAL_ID is not None: + # assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() + + # # Validate DG Proposal Submit event + # validate_dual_governance_submit_event( + # vote_events[0], + # proposal_id=EXPECTED_DG_PROPOSAL_ID, + # proposer=VOTING, + # executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + # metadata="TODO DG proposal description", + # proposal_calls=dual_governance_proposal_calls, + # emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], + # ) + + # # TODO validate all other voting events + + # validate_token_payout_event( + # event=vote_events[1], + # p=Payout( + # token_addr=MATIC_TOKEN, + # from_addr=AGENT, + # to_addr=LIDO_LABS_MS, + # amount=MATIC_IN_LIDO_LABS_AFTER), + # is_steth=False, + # emitted_by=AGENT + # ) if EXPECTED_DG_PROPOSAL_ID is not None: @@ -275,49 +274,49 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g chain.sleep(timelock.getAfterSubmitDelay() + 1) dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: - chain.sleep(timelock.getAfterScheduleDelay() + 1) - dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - display_dg_events(dg_tx) - dg_events = group_dg_events_from_receipt( - dg_tx, - timelock=EMERGENCY_PROTECTED_TIMELOCK, - admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - ) - assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT - assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT - - # TODO validate all DG events - - validate_set_spent_amount_event( - dg_events[0], - new_spent_amount=0, - emitted_by=ET_TRP_REGISTRY, - is_dg_event=True, - ) - - validate_set_limit_parameter_event( - dg_events[1], - limit=TRP_LIMIT_AFTER, - period_duration_month=TRP_PERIOD_DURATION_MONTHS, - period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, - emitted_by=ET_TRP_REGISTRY, - is_dg_event=True, - ) - - validate_staking_module_update_event( - event=dg_events[2], - module_item=StakingModuleItem( - id=SDVT_MODULE_ID, - name=SDVT_MODULE_NAME, - address=None, - target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, - module_fee=SDVT_MODULE_MODULE_FEE_BP, - treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, - priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), - emitted_by=STAKING_ROUTER, - is_dg_event=True - ) + # if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: + # chain.sleep(timelock.getAfterScheduleDelay() + 1) + # dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + # display_dg_events(dg_tx) + # dg_events = group_dg_events_from_receipt( + # dg_tx, + # timelock=EMERGENCY_PROTECTED_TIMELOCK, + # admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + # ) + # assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + # assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + + # # TODO validate all DG events + + # validate_set_spent_amount_event( + # dg_events[0], + # new_spent_amount=0, + # emitted_by=ET_TRP_REGISTRY, + # is_dg_event=True, + # ) + + # validate_set_limit_parameter_event( + # dg_events[1], + # limit=TRP_LIMIT_AFTER, + # period_duration_month=TRP_PERIOD_DURATION_MONTHS, + # period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, + # emitted_by=ET_TRP_REGISTRY, + # is_dg_event=True, + # ) + + # validate_staking_module_update_event( + # event=dg_events[2], + # module_item=StakingModuleItem( + # id=SDVT_MODULE_ID, + # name=SDVT_MODULE_NAME, + # address=None, + # target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, + # module_fee=SDVT_MODULE_MODULE_FEE_BP, + # treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, + # priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + # emitted_by=STAKING_ROUTER, + # is_dg_event=True + # ) # ========================================================================= # ==================== After DG proposal executed checks ================== @@ -328,41 +327,41 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() - assert trp_limit_after == TRP_LIMIT_AFTER - assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS - assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER - assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER - assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP - assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) - assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP - assert sdvt_module_after['id'] == SDVT_MODULE_ID - assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP - assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP - assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP - assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK - assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - assert sdvt_module_after['name'] == SDVT_MODULE_NAME - # additional checks to make sure no other fields were changed - assert sdvt_module_after['id'] == sdvt_module_before['id'] - assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] - assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] - assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] - assert sdvt_module_after['status'] == sdvt_module_before['status'] - assert sdvt_module_after['name'] == sdvt_module_before['name'] - assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] - assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] - assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] - assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] - assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] - assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] - assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) - assert len(sdvt_module_after.items()) == 13 - - # additional test for TRP ET factory behavior after the vote - trp_limit_test(stranger) + # assert trp_limit_after == TRP_LIMIT_AFTER + # assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS + # assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER + # assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + # assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP + # assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP + + # # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) + # assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP + # assert sdvt_module_after['id'] == SDVT_MODULE_ID + # assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + # assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + # assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + # assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + # assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + # assert sdvt_module_after['name'] == SDVT_MODULE_NAME + # # additional checks to make sure no other fields were changed + # assert sdvt_module_after['id'] == sdvt_module_before['id'] + # assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] + # assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] + # assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] + # assert sdvt_module_after['status'] == sdvt_module_before['status'] + # assert sdvt_module_after['name'] == sdvt_module_before['name'] + # assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] + # assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] + # assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] + # assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] + # assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] + # assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] + # assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) + # assert len(sdvt_module_after.items()) == 13 + + # # additional test for TRP ET factory behavior after the vote + # trp_limit_test(stranger) def trp_limit_test(stranger): @@ -386,7 +385,7 @@ def trp_limit_test(stranger): [to_spend + 1], stranger, ) - + # spend all step by step while to_spend > 0: create_and_enact_payment_motion( diff --git a/tests/upgrade_2025_11_24_mainnet_v3.py b/tests/upgrade_2025_11_24_mainnet_v3.py deleted file mode 100644 index 7c01f66ed..000000000 --- a/tests/upgrade_2025_11_24_mainnet_v3.py +++ /dev/null @@ -1,175 +0,0 @@ -from brownie import chain, interface -from brownie.network.transaction import TransactionReceipt -import pytest - -from utils.test.tx_tracing_helpers import ( - group_voting_events_from_receipt, - group_dg_events_from_receipt, - count_vote_items_by_events, - display_voting_events, - display_dg_events -) -from utils.evm_script import encode_call_script -from utils.voting import find_metadata_by_vote_id -from utils.ipfs import get_lido_vote_cid_from_str -from utils.dual_governance import PROPOSAL_STATUS -from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event - - -# ============================================================================ -# ============================== Import vote ================================= -# ============================================================================ -# TODO import voting script -# from scripts.vote_* import start_vote, get_vote_items - - -# ============================================================================ -# ============================== Constants =================================== -# ============================================================================ -# TODO list all contract addresses used in tests - do not use imports from config! -# NOTE: these addresses might have a different value on other chains - -VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" -AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" -EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" -DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" -DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" - -# TODO Set variable to None if item is not presented -EXPECTED_VOTE_ID = 1 -EXPECTED_DG_PROPOSAL_ID = 1 -EXPECTED_VOTE_EVENTS_COUNT = 1 -EXPECTED_DG_EVENTS_COUNT = 1 -IPFS_DESCRIPTION_HASH = "" - - -@pytest.fixture(scope="module") -def dual_governance_proposal_calls(): - # TODO Create all the dual governance calls that match the voting script - dg_items = [ - # # TODO 1.1. DG voting item 1 description - # agent_forward([ - # (dg_item_address_1, dg_item_encoded_input_1) - # ]), - # # TODO 1.2. DG voting item 2 description - # agent_forward([ - # (dg_item_address_2, dg_item_encoded_input_2) - # ]), - ] - - # Convert each dg_item to the expected format - proposal_calls = [] - for dg_item in dg_items: - target, data = dg_item # agent_forward returns (target, data) - proposal_calls.append({ - "target": target, - "value": 0, - "data": data - }) - - return proposal_calls - - -def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): - - # ======================================================================= - # ========================= Arrange variables =========================== - # ======================================================================= - voting = interface.Voting(VOTING) - agent = interface.Agent(AGENT) - timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) - dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) - - - # ========================================================================= - # ======================== Identify or Create vote ======================== - # ========================================================================= - if vote_ids_from_env: - vote_id = vote_ids_from_env[0] - if EXPECTED_VOTE_ID is not None: - assert vote_id == EXPECTED_VOTE_ID - elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: - vote_id = EXPECTED_VOTE_ID - else: - vote_id, _ = start_vote({"from": ldo_holder}, silent=True) - - _, call_script_items = get_vote_items() - onchain_script = voting.getVote(vote_id)["script"] - assert onchain_script == encode_call_script(call_script_items) - - - # ========================================================================= - # ============================= Execute Vote ============================== - # ========================================================================= - is_executed = voting.getVote(vote_id)["executed"] - if not is_executed: - # ======================================================================= - # ========================= Before voting checks ======================== - # ======================================================================= - # TODO add before voting checks - - - assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH - - vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) - display_voting_events(vote_tx) - vote_events = group_voting_events_from_receipt(vote_tx) - - - # ======================================================================= - # ========================= After voting checks ========================= - # ======================================================================= - # TODO add after voting tests - - - assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT - assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT - if EXPECTED_DG_PROPOSAL_ID is not None: - assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - - # TODO Validate DG Proposal Submit event - # validate_dual_governance_submit_event( - # vote_events[0], - # proposal_id=EXPECTED_DG_PROPOSAL_ID, - # proposer=VOTING, - # executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - # metadata="TODO DG proposal description", - # proposal_calls=dual_governance_proposal_calls, - # emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], - # ) - - # TODO validate all other voting events - - - if EXPECTED_DG_PROPOSAL_ID is not None: - details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) - if details["status"] != PROPOSAL_STATUS["executed"]: - # ========================================================================= - # ================== DG before proposal executed checks =================== - # ========================================================================= - # TODO add DG before proposal executed checks - - - if details["status"] == PROPOSAL_STATUS["submitted"]: - chain.sleep(timelock.getAfterSubmitDelay() + 1) - dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - - if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: - chain.sleep(timelock.getAfterScheduleDelay() + 1) - dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - display_dg_events(dg_tx) - dg_events = group_dg_events_from_receipt( - dg_tx, - timelock=EMERGENCY_PROTECTED_TIMELOCK, - admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - ) - assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT - assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT - - # TODO validate all DG events - - - # ========================================================================= - # ==================== After DG proposal executed checks ================== - # ========================================================================= - # TODO add DG after proposal executed checks From 69039c73154461f4a86768f4c592d3d3c7d834db Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 7 Nov 2025 07:52:42 +0000 Subject: [PATCH 028/178] fix: items order & items desc & DG desc --- scripts/vote_2025_11_24.py | 42 +++++++-------- tests/test_2025_11_24.py | 104 ++++++++++++++++++------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/scripts/vote_2025_11_24.py b/scripts/vote_2025_11_24.py index af4f89197..d49b7f5a2 100644 --- a/scripts/vote_2025_11_24.py +++ b/scripts/vote_2025_11_24.py @@ -2,12 +2,12 @@ # Vote 2025_11_24 === 1. DG PROPOPSAL === -I. Decrease Easy Track TRP limit -1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO -1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months +I. raise SDVT stake share limit +1.1. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 -II. Increase SDVT target share -1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +II. Reset Easy Track TRP limit +1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO +1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months === NON-DG ITEMS === III. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig @@ -66,19 +66,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_items = [ agent_forward([ - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), - ]), - agent_forward([ - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - set_limit_parameters( - limit=TRP_NEW_LIMIT, - period_duration_months=TRP_PERIOD_DURATION_MONTHS, - registry_address=ET_TRP_REGISTRY, - ), - ]), - agent_forward([ - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.1. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateStakingModule.encode_input( @@ -91,16 +79,28 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, ), ), - ]) + ]), + agent_forward([ + # 1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), + ]), + agent_forward([ + # 1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + set_limit_parameters( + limit=TRP_NEW_LIMIT, + period_duration_months=TRP_PERIOD_DURATION_MONTHS, + registry_address=ET_TRP_REGISTRY, + ), + ]), ] dg_call_script = submit_proposals([ - (dg_items, "TODO DG proposal description") + (dg_items, "Upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit") ]) vote_desc_items, call_script_items = zip( ( - "TODO 1. DG submission description", + "1. Submit a Dual Governance proposal to upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit", dg_call_script[0] ), ( diff --git a/tests/test_2025_11_24.py b/tests/test_2025_11_24.py index 930dc2c29..443c4844c 100644 --- a/tests/test_2025_11_24.py +++ b/tests/test_2025_11_24.py @@ -100,16 +100,6 @@ def dual_governance_proposal_calls(): # Create all the dual governance calls that match the voting script dg_items = [ - agent_forward([ - unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), - ]), - agent_forward([ - set_limit_parameters( - limit=TRP_LIMIT_AFTER, - period_duration_months=TRP_PERIOD_DURATION_MONTHS, - registry_address=ET_TRP_REGISTRY, - ), - ]), agent_forward([ ( staking_router.address, @@ -123,7 +113,17 @@ def dual_governance_proposal_calls(): SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, ), ), - ]) + ]), + agent_forward([ + unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), + ]), + agent_forward([ + set_limit_parameters( + limit=TRP_LIMIT_AFTER, + period_duration_months=TRP_PERIOD_DURATION_MONTHS, + registry_address=ET_TRP_REGISTRY, + ), + ]), ] @@ -221,7 +221,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g proposal_id=EXPECTED_DG_PROPOSAL_ID, proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="TODO DG proposal description", + metadata="Upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit", proposal_calls=dual_governance_proposal_calls, emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], ) @@ -248,18 +248,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # TODO add DG before proposal executed checks - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() - trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() - assert trp_limit_before == TRP_LIMIT_BEFORE - assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS - assert trp_already_spent_amount_before == TRP_ALREADY_SPENT_BEFORE - assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - TRP_ALREADY_SPENT_BEFORE - assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP - assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP - - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.1. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP assert sdvt_module_before['id'] == SDVT_MODULE_ID @@ -270,6 +259,17 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE assert sdvt_module_before['name'] == SDVT_MODULE_NAME + # 1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + # 1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() + trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() + assert trp_limit_before == TRP_LIMIT_BEFORE + assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS + assert trp_already_spent_amount_before == TRP_ALREADY_SPENT_BEFORE + assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - TRP_ALREADY_SPENT_BEFORE + assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP + assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP + if details["status"] == PROPOSAL_STATUS["submitted"]: chain.sleep(timelock.getAfterSubmitDelay() + 1) @@ -289,15 +289,29 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # TODO validate all DG events + validate_staking_module_update_event( + event=dg_events[0], + module_item=StakingModuleItem( + id=SDVT_MODULE_ID, + name=SDVT_MODULE_NAME, + address=None, + target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, + module_fee=SDVT_MODULE_MODULE_FEE_BP, + treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, + priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + emitted_by=STAKING_ROUTER, + is_dg_event=True + ) + validate_set_spent_amount_event( - dg_events[0], + dg_events[1], new_spent_amount=0, emitted_by=ET_TRP_REGISTRY, is_dg_event=True, ) validate_set_limit_parameter_event( - dg_events[1], + dg_events[2], limit=TRP_LIMIT_AFTER, period_duration_month=TRP_PERIOD_DURATION_MONTHS, period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, @@ -305,37 +319,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g is_dg_event=True, ) - validate_staking_module_update_event( - event=dg_events[2], - module_item=StakingModuleItem( - id=SDVT_MODULE_ID, - name=SDVT_MODULE_NAME, - address=None, - target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, - module_fee=SDVT_MODULE_MODULE_FEE_BP, - treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, - priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), - emitted_by=STAKING_ROUTER, - is_dg_event=True - ) - # ========================================================================= # ==================== After DG proposal executed checks ================== # ========================================================================= # TODO add DG after proposal executed checks - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() - trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() - assert trp_limit_after == TRP_LIMIT_AFTER - assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS - assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER - assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER - assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP - assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.1. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP assert sdvt_module_after['id'] == SDVT_MODULE_ID @@ -361,6 +350,17 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) assert len(sdvt_module_after.items()) == 13 + # 1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + # 1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() + trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() + assert trp_limit_after == TRP_LIMIT_AFTER + assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS + assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER + assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP + assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP + # additional test for TRP ET factory behavior after the vote trp_limit_test(stranger) From fe35b9120e59a98f975c29b0deade5708965543b Mon Sep 17 00:00:00 2001 From: dry914 Date: Wed, 12 Nov 2025 11:54:46 +0300 Subject: [PATCH 029/178] tests: remove legacy oracle tests, fix locator tests --- configs/config_mainnet.py | 9 +- interfaces/LidoLocator.json | 345 ++- interfaces/VaultHub.json | 2606 +++++++++++++++++ scripts/_vote_2025_MM_DD.py | 4 +- scripts/upgrade_2025_11_24_mainnet_v3.py | 4 +- tests/acceptance/test_accounting_oracle.py | 1 - tests/acceptance/test_legacy_oracle.py | 89 - tests/acceptance/test_locator.py | 11 +- tests/conftest.py | 1 - tests/regression/test_permissions.py | 7 - tests/snapshot/test_first_slots.py | 1 - tests/snapshot/test_legacy_oracle_snapshot.py | 194 -- utils/config.py | 12 +- utils/repo.py | 4 - utils/test/legacy_oracle_report_helpers.py | 12 - 15 files changed, 2968 insertions(+), 332 deletions(-) create mode 100644 interfaces/VaultHub.json delete mode 100644 tests/acceptance/test_legacy_oracle.py delete mode 100644 tests/snapshot/test_legacy_oracle_snapshot.py delete mode 100644 utils/test/legacy_oracle_report_helpers.py diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 5f3a64b42..ec5b11416 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -108,7 +108,7 @@ # LidoLocator LIDO_LOCATOR = "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" -LIDO_LOCATOR_IMPL = "0x2C298963FB763f74765829722a1ebe0784f4F5Cf" +LIDO_LOCATOR_IMPL = "0x9fAb5b58c1Bc180533b716B5c1D078e23cD06301" # TODO fix after mainnet deployment # Other upgrade addresses LIDO_V2_UPGRADE_TEMPLATE = "0xa818fF9EC93122Bf9401ab4340C42De638CD600a" @@ -201,7 +201,7 @@ EXIT_EVENTS_LOOKBACK_WINDOW_IN_SLOTS = 14 * 7200 # 14 days # OracleReportSanityChecker -ORACLE_REPORT_SANITY_CHECKER = "0x6232397ebac4f5772e53285B26c47914E9461E75" +ORACLE_REPORT_SANITY_CHECKER = "0x1C1817e6f03F543ff4Bd86C25a45e921800DEf7a" # TODO fix after mainnet deployment APPEARED_VALIDATORS_PER_DAY_LIMIT = 1800 EXITED_VALIDATORS_PER_DAY_LIMIT = 3600 ANNUAL_BALANCE_INCREASE_BP_LIMIT = 1000 # 10% @@ -216,7 +216,7 @@ CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 50 # Burner -BURNER = "0xD15a672319Cf0352560eE76d9e89eAB0889046D3" +BURNER = "0xb70b8cC263361FBf8b60E20569be809809Ff1537" # TODO fix after mainnet deployment TOTAL_NON_COVER_SHARES_BURNT = 32145684728326685744 TOTAL_COVER_SHARES_BURNT = 0 @@ -522,3 +522,6 @@ "WITHDRAWAL_BLOCKERS": (WITHDRAWAL_QUEUE, VALIDATORS_EXIT_BUS_ORACLE, TRIGGERABLE_WITHDRAWALS_GATEWAY), } } + +# Lido V3 - stVaults +VAULT_HUB = "0xe3c5C1fb61C6c81cb305D4580c53Cfa4BcB7899b" # TODO fix after mainnet deployment diff --git a/interfaces/LidoLocator.json b/interfaces/LidoLocator.json index 5bb321206..a25962cf3 100644 --- a/interfaces/LidoLocator.json +++ b/interfaces/LidoLocator.json @@ -1,2 +1,343 @@ -[{"inputs":[{"components":[{"internalType":"address","name":"accountingOracle","type":"address"},{"internalType":"address","name":"depositSecurityModule","type":"address"},{"internalType":"address","name":"elRewardsVault","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"oracleReportSanityChecker","type":"address"},{"internalType":"address","name":"postTokenRebaseReceiver","type":"address"},{"internalType":"address","name":"burner","type":"address"},{"internalType":"address","name":"stakingRouter","type":"address"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"address","name":"validatorsExitBusOracle","type":"address"},{"internalType":"address","name":"withdrawalQueue","type":"address"},{"internalType":"address","name":"withdrawalVault","type":"address"},{"internalType":"address","name":"oracleDaemonConfig","type":"address"},{"internalType":"address","name":"validatorExitDelayVerifier","type":"address"},{"internalType":"address","name":"triggerableWithdrawalsGateway","type":"address"}],"internalType":"struct LidoLocator.Config","name":"_config","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"accountingOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"coreComponents","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositSecurityModule","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"elRewardsVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"legacyOracle","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":"oracleDaemonConfig","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleReportComponentsForLido","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleReportSanityChecker","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"postTokenRebaseReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"triggerableWithdrawalsGateway","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"validatorExitDelayVerifier","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"validatorsExitBusOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalQueue","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] - +[ + { + "inputs": [], + "name": "accounting", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "accountingOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "burner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "coreComponents", + "outputs": [ + { + "internalType": "address", + "name": "elRewardsVault", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleReportSanityChecker", + "type": "address" + }, + { + "internalType": "address", + "name": "stakingRouter", + "type": "address" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalQueue", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalVault", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "depositSecurityModule", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "elRewardsVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lazyOracle", + "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": "operatorGrid", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleDaemonConfig", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleReportComponents", + "outputs": [ + { + "internalType": "address", + "name": "accountingOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleReportSanityChecker", + "type": "address" + }, + { + "internalType": "address", + "name": "burner", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalQueue", + "type": "address" + }, + { + "internalType": "address", + "name": "postTokenRebaseReceiver", + "type": "address" + }, + { + "internalType": "address", + "name": "stakingRouter", + "type": "address" + }, + { + "internalType": "address", + "name": "vaultHub", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleReportSanityChecker", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "postTokenRebaseReceiver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "predepositGuarantee", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingRouter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "validatorsExitBusOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultFactory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultHub", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalQueue", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "wstETH", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/VaultHub.json b/interfaces/VaultHub.json new file mode 100644 index 000000000..d10e932d8 --- /dev/null +++ b/interfaces/VaultHub.json @@ -0,0 +1,2606 @@ +[ + { + "inputs": [ + { + "internalType": "contract ILidoLocator", + "name": "_locator", + "type": "address" + }, + { + "internalType": "contract ILido", + "name": "_lido", + "type": "address" + }, + { + "internalType": "contract IHashConsensus", + "name": "_consensusContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxRelativeShareLimitBP", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "AlreadyConnected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "totalValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "AmountExceedsTotalValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "withdrawable", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requested", + "type": "uint256" + } + ], + "name": "AmountExceedsWithdrawableValue", + "type": "error" + }, + { + "inputs": [], + "name": "BadDebtSocializationNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ForcedValidatorExitNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "InsufficientSharesToBurn", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "InsufficientStagedBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "etherToLock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLockableValue", + "type": "uint256" + } + ], + "name": "InsufficientValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "valueBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxValueBP", + "type": "uint256" + } + ], + "name": "InvalidBasisPoints", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NoFundsForForceRebalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "unsettledLidoFees", + "type": "uint256" + } + ], + "name": "NoFundsToSettleLidoFees", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liabilityShares", + "type": "uint256" + } + ], + "name": "NoLiabilitySharesShouldBeLeft", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NoReasonForForceRebalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "unsettledLidoFees", + "type": "uint256" + } + ], + "name": "NoUnsettledLidoFeesShouldBeLeft", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NoUnsettledLidoFeesToSettle", + "type": "error" + }, + { + "inputs": [], + "name": "NotAuthorized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NotConnectedToHub", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "PDGNotDepositor", + "type": "error" + }, + { + "inputs": [], + "name": "PartialValidatorWithdrawalNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "PauseIntentAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "PauseIntentAlreadyUnset", + "type": "error" + }, + { + "inputs": [], + "name": "PauseUntilMustBeInFuture", + "type": "error" + }, + { + "inputs": [], + "name": "PausedExpected", + "type": "error" + }, + { + "inputs": [], + "name": "ResumedExpected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "value", + "type": "int256" + } + ], + "name": "SafeCastOverflowedIntToUint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "expectedSharesAfterMint", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + } + ], + "name": "ShareLimitExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxShareLimit", + "type": "uint256" + } + ], + "name": "ShareLimitTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultHubNotPendingOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expectedBalance", + "type": "uint256" + } + ], + "name": "VaultInsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultIsDisconnecting", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "totalValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newRebalanceThresholdBP", + "type": "uint256" + } + ], + "name": "VaultMintingCapacityExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultNotFactoryDeployed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultOssified", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultReportStale", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroArgument", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPauseDuration", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vaultDonor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vaultAcceptor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtShares", + "type": "uint256" + } + ], + "name": "BadDebtSocialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtShares", + "type": "uint256" + } + ], + "name": "BadDebtWrittenOffToBeInternalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "pauseIntent", + "type": "bool" + } + ], + "name": "BeaconChainDepositsPauseIntentSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "BurnedSharesOnVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "pubkeys", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "address", + "name": "refundRecipient", + "type": "address" + } + ], + "name": "ForcedValidatorExitTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferred", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cumulativeLidoFees", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "settledLidoFees", + "type": "uint256" + } + ], + "name": "LidoFeesSettled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOfShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lockedAmount", + "type": "uint256" + } + ], + "name": "MintedSharesOnVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "name": "VaultConnected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + } + ], + "name": "VaultConnectionUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashingReserve", + "type": "uint256" + } + ], + "name": "VaultDisconnectAborted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultDisconnectCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultDisconnectInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "preInfraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "preLiquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "preReservationFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "name": "VaultFeesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "inOutDelta", + "type": "int256" + } + ], + "name": "VaultInOutDeltaUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + } + ], + "name": "VaultOwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sharesBurned", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "etherWithdrawn", + "type": "uint256" + } + ], + "name": "VaultRebalanced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redemptionShares", + "type": "uint256" + } + ], + "name": "VaultRedemptionSharesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportTotalValue", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "reportInOutDelta", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportCumulativeLidoFees", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportLiabilityShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportMaxLiabilityShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportSlashingReserve", + "type": "uint256" + } + ], + "name": "VaultReportApplied", + "type": "event" + }, + { + "inputs": [], + "name": "BAD_DEBT_MASTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONNECT_DEPOSIT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONSENSUS_CONTRACT", + "outputs": [ + { + "internalType": "contract IHashConsensus", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO", + "outputs": [ + { + "internalType": "contract ILido", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_RELATIVE_SHARE_LIMIT_BP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_INFINITELY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REDEMPTION_MASTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REPORT_FRESHNESS_DELTA", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VALIDATOR_EXIT_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VAULT_MASTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reportTotalValue", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "_reportInOutDelta", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "_reportCumulativeLidoFees", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reportLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reportMaxLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reportSlashingReserve", + "type": "uint256" + } + ], + "name": "applyVaultReport", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "badDebtToInternalize", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "burnShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "collectERC20FromVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "connectVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "decreaseInternalizedBadDebt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "disconnect", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "forceRebalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_pubkeys", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_refundRecipient", + "type": "address" + } + ], + "name": "forceValidatorExit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "fund", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getResumeSinceTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMembers", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "healthShortfallShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_badDebtVault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxSharesToInternalize", + "type": "uint256" + } + ], + "name": "internalizeBadDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isPendingDisconnect", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isReportFresh", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isVaultConnected", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isVaultHealthy", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "latestReport", + "outputs": [ + { + "components": [ + { + "internalType": "uint104", + "name": "totalValue", + "type": "uint104" + }, + { + "internalType": "int104", + "name": "inOutDelta", + "type": "int104" + }, + { + "internalType": "uint48", + "name": "timestamp", + "type": "uint48" + } + ], + "internalType": "struct VaultHub.Report", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "liabilityShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "locked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "maxLockableValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "mintShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "obligations", + "outputs": [ + { + "internalType": "uint256", + "name": "sharesToBurn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feesToSettle", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "obligationsShortfallValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "pauseBeaconChainDeposits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_duration", + "type": "uint256" + } + ], + "name": "pauseFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pauseUntilInclusive", + "type": "uint256" + } + ], + "name": "pauseUntil", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + } + ], + "name": "proveUnknownValidatorToPDG", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shares", + "type": "uint256" + } + ], + "name": "rebalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_pubkeys", + "type": "bytes" + } + ], + "name": "requestValidatorExit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resume", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "resumeBeaconChainDeposits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_liabilitySharesTarget", + "type": "uint256" + } + ], + "name": "setLiabilitySharesTarget", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "settleLidoFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "settleableLidoFeesValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_badDebtVault", + "type": "address" + }, + { + "internalType": "address", + "name": "_vaultAcceptor", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxSharesToSocialize", + "type": "uint256" + } + ], + "name": "socializeBadDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "int256", + "name": "_deltaValue", + "type": "int256" + } + ], + "name": "totalMintingCapacityShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "totalValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "transferAndBurnShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_newOwner", + "type": "address" + } + ], + "name": "transferVaultOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_pubkeys", + "type": "bytes" + }, + { + "internalType": "uint64[]", + "name": "_amountsInGwei", + "type": "uint64[]" + }, + { + "internalType": "address", + "name": "_refundRecipient", + "type": "address" + } + ], + "name": "triggerValidatorWithdrawals", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reservationFeeBP", + "type": "uint256" + } + ], + "name": "updateConnection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "vaultByIndex", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultConnection", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "vaultIndex", + "type": "uint96" + }, + { + "internalType": "uint48", + "name": "disconnectInitiatedTs", + "type": "uint48" + }, + { + "internalType": "uint16", + "name": "reserveRatioBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "forcedRebalanceThresholdBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "infraFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "reservationFeeBP", + "type": "uint16" + }, + { + "internalType": "bool", + "name": "beaconChainDepositsPauseIntent", + "type": "bool" + } + ], + "internalType": "struct VaultHub.VaultConnection", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultRecord", + "outputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint104", + "name": "totalValue", + "type": "uint104" + }, + { + "internalType": "int104", + "name": "inOutDelta", + "type": "int104" + }, + { + "internalType": "uint48", + "name": "timestamp", + "type": "uint48" + } + ], + "internalType": "struct VaultHub.Report", + "name": "report", + "type": "tuple" + }, + { + "internalType": "uint96", + "name": "maxLiabilityShares", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "liabilityShares", + "type": "uint96" + }, + { + "components": [ + { + "internalType": "int104", + "name": "value", + "type": "int104" + }, + { + "internalType": "int104", + "name": "valueOnRefSlot", + "type": "int104" + }, + { + "internalType": "uint48", + "name": "refSlot", + "type": "uint48" + } + ], + "internalType": "struct DoubleRefSlotCache.Int104WithCache[2]", + "name": "inOutDelta", + "type": "tuple[2]" + }, + { + "internalType": "uint128", + "name": "minimalReserve", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "redemptionShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "cumulativeLidoFees", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "settledLidoFees", + "type": "uint128" + } + ], + "internalType": "struct VaultHub.VaultRecord", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "voluntaryDisconnect", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_ether", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "withdrawableValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] diff --git a/scripts/_vote_2025_MM_DD.py b/scripts/_vote_2025_MM_DD.py index a3047aa7e..1fb3c7969 100644 --- a/scripts/_vote_2025_MM_DD.py +++ b/scripts/_vote_2025_MM_DD.py @@ -48,11 +48,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: # (dg_item_address_2, dg_item_encoded_input_2) # ]), # ] - # + # # dg_call_script = submit_proposals([ # (dg_items, "TODO DG proposal description") # ]) - # + # # vote_desc_items, call_script_items = zip( # ( # "TODO 1. DG submission description", diff --git a/scripts/upgrade_2025_11_24_mainnet_v3.py b/scripts/upgrade_2025_11_24_mainnet_v3.py index 28f3120fa..7c9e424e3 100644 --- a/scripts/upgrade_2025_11_24_mainnet_v3.py +++ b/scripts/upgrade_2025_11_24_mainnet_v3.py @@ -69,11 +69,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: contract_dg_items = interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).getVoteItems() dg_items = [] - for desc, call_script in contract_dg_items: # TODO looks like this descriptions are not used + for desc, call_script in contract_dg_items: dg_items.append((call_script[0], '0x' + call_script[1].hex())) dg_call_script = submit_proposals([ - (dg_items, "TODO DG proposal description") # TODO looks like this description is not used + (dg_items, "TODO DG proposal description")# TODO take from next-vote ]) vote_desc_items.append("TODO DG submission description") diff --git a/tests/acceptance/test_accounting_oracle.py b/tests/acceptance/test_accounting_oracle.py index 919b48287..151c04d0b 100644 --- a/tests/acceptance/test_accounting_oracle.py +++ b/tests/acceptance/test_accounting_oracle.py @@ -31,7 +31,6 @@ def test_proxy(contract): def test_constants(contract): assert contract.LIDO() == contracts.lido assert contract.LOCATOR() == contracts.lido_locator - assert contract.LEGACY_ORACLE() == contracts.legacy_oracle assert contract.EXTRA_DATA_FORMAT_EMPTY() == 0 assert contract.EXTRA_DATA_FORMAT_LIST() == 1 assert contract.EXTRA_DATA_TYPE_STUCK_VALIDATORS() == 1 diff --git a/tests/acceptance/test_legacy_oracle.py b/tests/acceptance/test_legacy_oracle.py deleted file mode 100644 index 49848227c..000000000 --- a/tests/acceptance/test_legacy_oracle.py +++ /dev/null @@ -1,89 +0,0 @@ -import pytest -from brownie import ZERO_ADDRESS, interface, chain, reverts # type: ignore - -from utils.config import ( - contracts, - LEGACY_ORACLE, - LEGACY_ORACLE_IMPL, - HASH_CONSENSUS_FOR_AO, - ACCOUNTING_ORACLE, - ORACLE_ARAGON_APP_ID, - ARAGON_EVMSCRIPT_REGISTRY, - CHAIN_SLOTS_PER_EPOCH, - CHAIN_SECONDS_PER_SLOT, - CHAIN_GENESIS_TIME, - AO_EPOCHS_PER_FRAME, -) - -lastSeenTotalPooledEther = 5879742251110033487920093 - -@pytest.fixture(scope="module") -def contract() -> interface.LegacyOracle: - return interface.LegacyOracle(LEGACY_ORACLE) - - -def test_links(contract): - assert contract.getLido() == contracts.lido - assert contract.getAccountingOracle() == contracts.accounting_oracle - assert contract.getEVMScriptRegistry() == ARAGON_EVMSCRIPT_REGISTRY - - -def test_aragon(contract): - proxy = interface.AppProxyUpgradeable(contract) - assert proxy.implementation() == LEGACY_ORACLE_IMPL - assert contract.kernel() == contracts.kernel - assert contract.appId() == ORACLE_ARAGON_APP_ID - assert contract.hasInitialized() == True - assert contract.isPetrified() == False - - -def test_versioned(contract): - assert contract.getContractVersion() == 4 - - -def test_initialize(contract): - with reverts("INIT_ALREADY_INITIALIZED"): - contract.initialize(contracts.lido_locator, HASH_CONSENSUS_FOR_AO, {"from": contracts.voting}) - - -def test_finalize_upgrade(contract): - with reverts("WRONG_BASE_VERSION"): - contract.finalizeUpgrade_v4(ACCOUNTING_ORACLE, {"from": contracts.voting}) - - -def test_petrified(): - impl = interface.LegacyOracle(LEGACY_ORACLE_IMPL) - with reverts("INIT_ALREADY_INITIALIZED"): - impl.initialize(contracts.lido_locator, HASH_CONSENSUS_FOR_AO, {"from": contracts.voting}) - - with reverts("WRONG_BASE_VERSION"): - impl.finalizeUpgrade_v4(ACCOUNTING_ORACLE, {"from": contracts.voting}) - - -def test_recoverability(contract): - assert contract.getRecoveryVault() == ZERO_ADDRESS - assert contract.allowRecoverability(contracts.ldo_token) == True - - -def test_legacy_oracle_state(contract): - reported_delta = contract.getLastCompletedReportDelta() - assert reported_delta["postTotalPooledEther"] > lastSeenTotalPooledEther - assert reported_delta["preTotalPooledEther"] >= lastSeenTotalPooledEther - assert reported_delta["timeElapsed"] >= 86400 - - current_frame = contract.getCurrentFrame() - assert current_frame["frameEpochId"] > 0 - assert current_frame["frameStartTime"] > 0 - assert current_frame["frameEndTime"] > 0 - - assert contract.getLastCompletedEpochId() > 0 - - assert contract.getInitializationBlock() > 0 - assert contract.getInitializationBlock() <= chain.height - - oracle_beacon_spec = contracts.legacy_oracle.getBeaconSpec() - - assert oracle_beacon_spec["epochsPerFrame"] == AO_EPOCHS_PER_FRAME - assert oracle_beacon_spec["slotsPerEpoch"] == CHAIN_SLOTS_PER_EPOCH - assert oracle_beacon_spec["secondsPerSlot"] == CHAIN_SECONDS_PER_SLOT - assert oracle_beacon_spec["genesisTime"] == CHAIN_GENESIS_TIME diff --git a/tests/acceptance/test_locator.py b/tests/acceptance/test_locator.py index e4e22a68a..18b1b4d39 100644 --- a/tests/acceptance/test_locator.py +++ b/tests/acceptance/test_locator.py @@ -19,10 +19,9 @@ def test_addresses(contract): assert contract.accountingOracle() == contracts.accounting_oracle assert contract.depositSecurityModule() == contracts.deposit_security_module assert contract.elRewardsVault() == contracts.execution_layer_rewards_vault - assert contract.legacyOracle() == contracts.legacy_oracle assert contract.lido() == contracts.lido assert contract.oracleReportSanityChecker() == contracts.oracle_report_sanity_checker - assert contract.postTokenRebaseReceiver() == contracts.token_rate_notifier + assert contract.postTokenRebaseReceiver() == "0x0000000000000000000000000000000000000000" # TODO contracts.token_rate_notifier assert contract.burner() == contracts.burner assert contract.stakingRouter() == contracts.staking_router assert contract.treasury() == contracts.agent @@ -40,12 +39,12 @@ def test_addresses(contract): contracts.withdrawal_vault, ) - assert contract.oracleReportComponentsForLido() == ( + assert contract.oracleReportComponents() == ( contracts.accounting_oracle, - contracts.execution_layer_rewards_vault, contracts.oracle_report_sanity_checker, contracts.burner, contracts.withdrawal_queue, - contracts.withdrawal_vault, - contracts.token_rate_notifier, + "0x0000000000000000000000000000000000000000", # TODO contracts.postTokenRebaseReceiver, + contracts.staking_router, + contracts.vault_hub, ) diff --git a/tests/conftest.py b/tests/conftest.py index 6453db18d..4a2dc83bc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -236,7 +236,6 @@ def parse_events_from_local_abi(): HASH_CONSENSUS_FOR_AO, HASH_CONSENSUS_FOR_VEBO, ], - "LegacyOracle": [LEGACY_ORACLE, LEGACY_ORACLE_IMPL], "Lido": [LIDO, LIDO_IMPL], "LidoLocator": [LIDO_LOCATOR], "LidoExecutionLayerRewardsVault": [EXECUTION_LAYER_REWARDS_VAULT], diff --git a/tests/regression/test_permissions.py b/tests/regression/test_permissions.py index 8e27dabf3..ff216ec32 100644 --- a/tests/regression/test_permissions.py +++ b/tests/regression/test_permissions.py @@ -44,7 +44,6 @@ WITHDRAWAL_QUEUE, BURNER, LIDO_LOCATOR, - LEGACY_ORACLE, SIMPLE_DVT, CSM_ADDRESS, CS_ACCOUNTING_ADDRESS, @@ -320,12 +319,6 @@ def protocol_permissions(): "STAKING_ROUTER_ROLE": [STAKING_ROUTER, EASYTRACK_EVMSCRIPT_EXECUTOR], }, }, - LEGACY_ORACLE: { - "contract_name": "LegacyOracle", - "contract": contracts.legacy_oracle, - "type": "AragonApp", - "roles": {}, - }, CSM_ADDRESS: { "contract_name": "CSModule", "contract": contracts.csm, diff --git a/tests/snapshot/test_first_slots.py b/tests/snapshot/test_first_slots.py index 390eb573d..f1e119966 100644 --- a/tests/snapshot/test_first_slots.py +++ b/tests/snapshot/test_first_slots.py @@ -92,7 +92,6 @@ def _snap(): for contract in ( contracts.lido, contracts.node_operators_registry, - contracts.legacy_oracle, contracts.deposit_security_module, contracts.execution_layer_rewards_vault, contracts.withdrawal_vault, diff --git a/tests/snapshot/test_legacy_oracle_snapshot.py b/tests/snapshot/test_legacy_oracle_snapshot.py deleted file mode 100644 index 085739b30..000000000 --- a/tests/snapshot/test_legacy_oracle_snapshot.py +++ /dev/null @@ -1,194 +0,0 @@ -from contextlib import contextmanager -from typing import Any, Callable, Sequence, TypedDict - -import brownie -import pytest -from brownie import chain, web3, accounts -from brownie.network.account import Account -from pytest_check import check -from typing_extensions import Protocol - -from tests.conftest import Helpers -from utils.config import contracts, LDO_TOKEN -from utils.evm_script import EMPTY_CALLSCRIPT -from utils.test.governance_helpers import execute_vote_and_process_dg_proposals -from utils.test.snapshot_helpers import _chain_snapshot - -from .utils import get_slot - - -class Frame(TypedDict): - """A snapshot of the state before and after an action.""" - - snap: dict[str, Any] - func: str - - -Stack = Sequence[Frame] -SnapshotFn = Callable[[], dict] - - -class SandwichFn(Protocol): - @staticmethod - def __call__( - actions_list: Sequence[Callable], - snapshot_fn: SnapshotFn = ..., - snapshot_block: int = ..., - ) -> tuple[Stack, Stack]: - ... - - -def test_legacy_oracle_no_changes_in_views(sandwich_upgrade: SandwichFn): - """Test that no views change during the upgrade process""" - - stacks = sandwich_upgrade([]) - _stacks_equal(stacks) - - -@pytest.fixture(scope="module") -def do_snapshot( - interface, - some_contract: Account, -): - oracle = contracts.legacy_oracle - - def _snap(): - block = chain.height - res = {} - - with brownie.multicall(block_identifier=block): - res |= { - "block_number": chain.height, - "chain_time": web3.eth.get_block(chain.height)["timestamp"], - "address": oracle.address, - # AppProxyUpgradeable - "isDepositable": interface.AppProxyUpgradeable(oracle.address).isDepositable(), - # Oracle - "allowRecoverability(LDO)": oracle.allowRecoverability(LDO_TOKEN), - "allowRecoverability(SOME_CONTRACT)": oracle.allowRecoverability(some_contract), - "getBeaconSpec": oracle.getBeaconSpec(), - "getCurrentEpochId": oracle.getCurrentEpochId(), - "getCurrentFrame": oracle.getCurrentFrame(), - "getLastCompletedEpochId": oracle.getLastCompletedEpochId(), - "getLastCompletedReportDelta": oracle.getLastCompletedReportDelta(), - "getLido": oracle.getLido(), - # AragonApp - # "getRecoveryVault": oracle.getRecoveryVault(), - "kernel": oracle.kernel(), - "appId": oracle.appId(), - "getEVMScriptExecutor(nil)": oracle.getEVMScriptExecutor(EMPTY_CALLSCRIPT), - "getEVMScriptRegistry": oracle.getEVMScriptRegistry(), - "getInitializationBlock": oracle.getInitializationBlock(), - "hasInitialized": oracle.hasInitialized(), - } - - for v1_slot in ( - # LidoOracle.sol - "lido.LidoOracle.allowedBeaconBalanceAnnualRelativeIncrease", - "lido.LidoOracle.allowedBeaconBalanceDecrease", - "lido.LidoOracle.beaconReportReceiver", - "lido.LidoOracle.beaconSpec", - "lido.LidoOracle.expectedEpochId", - "lido.LidoOracle.lastCompletedEpochId", - "lido.LidoOracle.lastReportedEpochId", - "lido.LidoOracle.lido", - "lido.LidoOracle.postCompletedTotalPooledEther", - "lido.LidoOracle.preCompletedTotalPooledEther", - "lido.LidoOracle.quorum", - "lido.LidoOracle.reportsBitMask", - "lido.LidoOracle.timeElapsed", - # AragonApp.sol - "aragonOS.appStorage.kernel", - "aragonOS.appStorage.appId", - ): - res[v1_slot] = get_slot( - oracle.address, - name=v1_slot, - block=block, - ) - - res["members"] = get_slot( - oracle.address, - pos=0, - as_list=True, - block=block, - ) - - return res - - return _snap - - -@pytest.fixture(scope="module") -def some_contract(accounts) -> Account: - # Multicall3 contract deployed almost on the every network on the same address - return accounts.at("0xcA11bde05977b3631167028862bE2a173976CA11", force=True) - - -@pytest.fixture(scope="module") -def sandwich_upgrade( - do_snapshot: SnapshotFn, - far_block: int, - far_ts: int, - helpers: Helpers, - vote_ids_from_env: Any, - dg_proposal_ids_from_env: Any -) -> SandwichFn: - """Snapshot the state before and after the upgrade and return the two frames""" - - def _do( - actions_list: Sequence[Callable], - snapshot_fn=do_snapshot, - snapshot_block=far_block, - ): - def _actions_snaps(): - _sleep_till_block(snapshot_block, far_ts) - - yield Frame(snap=snapshot_fn(), func="init") - - for action_fn in actions_list: - action_fn() - yield Frame( - snap=snapshot_fn(), - func=repr(action_fn), - ) - - with _chain_snapshot(): - before = tuple(_actions_snaps()) - - execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env) - - # do not call _chain_snapshot here to be able to interact with the environment in the test - after = tuple(_actions_snaps()) - - return before, after - - return _do - - -@pytest.fixture(scope="module") -def far_block() -> int: - return chain.height + 1_000 - - -@pytest.fixture(scope="module") -def far_ts() -> int: - return chain.time() + 14 * 24 * 60 * 60 # 14 days - - -def _sleep_till_block(block: int, ts: int) -> None: - curr_block = web3.eth.get_block_number() - - if curr_block > block: - raise ValueError(f"Current block {curr_block} is greater than the target block {block}") - - print(f"Forwarding chain to block {block}, may take a while...") - chain.mine(block - curr_block, timestamp=ts) - - -def _stacks_equal(stacks: tuple[Stack, Stack]) -> None: - """Compare two stacks, asserting that they are equal""" - - for v1_frame, v2_frame in zip(*stacks, strict=True): - with check: # soft asserts - assert v1_frame["snap"] == v2_frame["snap"], f"Snapshots after {v1_frame['func']} are not equal" diff --git a/utils/config.py b/utils/config.py index 573bd42b2..7898a14e9 100644 --- a/utils/config.py +++ b/utils/config.py @@ -258,14 +258,6 @@ def cs_strikes(self) -> interface.CSStrikes: def sandbox(self) -> interface.SimpleDVT: return interface.Sandbox(SANDBOX) - @property - def legacy_oracle(self) -> interface.LegacyOracle: - return interface.LegacyOracle(LEGACY_ORACLE) - - @property - def token_rate_notifier(self) -> interface.TokenRateNotifier: - return interface.LegacyOracle(L1_TOKEN_RATE_NOTIFIER) - @property def deposit_security_module_v1(self) -> interface.DepositSecurityModule: return interface.DepositSecurityModuleV1(DEPOSIT_SECURITY_MODULE_V1) @@ -314,6 +306,10 @@ def triggerable_withdrawals_gateway(self): def withdrawal_queue(self) -> interface.WithdrawalQueueERC721: return interface.WithdrawalQueueERC721(WITHDRAWAL_QUEUE) + @property + def vault_hub(self) -> interface.VaultHub: + return interface.VaultHub(VAULT_HUB) + @property def lido_locator(self) -> interface.LidoLocator: return interface.LidoLocator(LIDO_LOCATOR) diff --git a/utils/repo.py b/utils/repo.py index c44f6ec16..4d0d80403 100644 --- a/utils/repo.py +++ b/utils/repo.py @@ -25,10 +25,6 @@ def add_implementation_to_voting_app_repo(version, address, content_uri): return _add_implementation_to_repo(contracts.voting_app_repo, version, address, content_uri) -def add_implementation_to_oracle_app_repo(version, address, content_uri): - return _add_implementation_to_repo(contracts.oracle_app_repo, version, address, content_uri) - - def create_new_app_repo(name, manager, version, address, content_uri): apm_registry = contracts.apm_registry diff --git a/utils/test/legacy_oracle_report_helpers.py b/utils/test/legacy_oracle_report_helpers.py deleted file mode 100644 index 025a985b2..000000000 --- a/utils/test/legacy_oracle_report_helpers.py +++ /dev/null @@ -1,12 +0,0 @@ -from brownie import interface, accounts -from utils.config import contracts, lido_dao_legacy_oracle - - -def legacy_report(_epochId, _beaconBalance, _beaconValidators): - oracle = interface.LidoOracle(lido_dao_legacy_oracle) - quorum = oracle.getQuorum() - members = oracle.getOracleMembers() - - for i in range(quorum): - member = accounts.at(members[i], force=True) - oracle.reportBeacon(_epochId, _beaconBalance, _beaconValidators, {"from": member}) From decc269f5d6d54800c9e7ddfa8a88e5c6ad7a526 Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 13 Nov 2025 19:37:52 +0300 Subject: [PATCH 030/178] test: fix lido tests --- configs/config_mainnet.py | 2 +- interfaces/Lido.json | 1927 +++++++++++++++++++++- scripts/upgrade_2025_11_24_mainnet_v3.py | 37 +- tests/acceptance/test_lido.py | 10 +- 4 files changed, 1935 insertions(+), 41 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index ec5b11416..f445b9921 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -121,7 +121,7 @@ WITHDRAWAL_CREDENTIALS = "0x010000000000000000000000b9d7934878b5fb9610b3fe8a5e441e8fad7e293f" # Lido -LIDO_IMPL = "0x17144556fd3424EDC8Fc8A4C940B2D04936d17eb" +LIDO_IMPL = "0xE1CF94253EF9F9d01FDd5fDdd54fDE1D1Fd7F5A6" # TODO fix after mainnet deployment # see Lido's proxy appId() LIDO_ARAGON_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" LIDO_MAX_STAKE_LIMIT_ETH = 150_000 diff --git a/interfaces/Lido.json b/interfaces/Lido.json index c28e25b8b..0389d73d4 100644 --- a/interfaces/Lido.json +++ b/interfaces/Lido.json @@ -1 +1,1926 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_CONTROL_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_maxStakeLimit","type":"uint256"},{"name":"_stakeLimitIncreasePerBlock","type":"uint256"}],"name":"setStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"finalizeUpgrade_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newDepositedValidators","type":"uint256"}],"name":"unsafeChangeDepositedValidators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"receiveELRewards","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentStakeLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStakeLimitFullInfo","outputs":[{"name":"isStakingPaused","type":"bool"},{"name":"isStakingLimitSet","type":"bool"},{"name":"currentStakeLimit","type":"uint256"},{"name":"maxStakeLimit","type":"uint256"},{"name":"maxStakeLimitGrowthBlocks","type":"uint256"},{"name":"prevStakeLimit","type":"uint256"},{"name":"prevStakeBlockNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferSharesFrom","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"receiveWithdrawals","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"eip712Domain","outputs":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getContractVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getEIP712StETH","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDepositsCount","type":"uint256"},{"name":"_stakingModuleId","type":"uint256"},{"name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"removeStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_reportTimestamp","type":"uint256"},{"name":"_timeElapsed","type":"uint256"},{"name":"_clValidators","type":"uint256"},{"name":"_clBalance","type":"uint256"},{"name":"_withdrawalVaultBalance","type":"uint256"},{"name":"_elRewardsVaultBalance","type":"uint256"},{"name":"_sharesRequestedToBurn","type":"uint256"},{"name":"_withdrawalFinalizationBatches","type":"uint256[]"},{"name":"_simulatedShareRate","type":"uint256"}],"name":"handleOracleReport","outputs":[{"name":"postRebaseAmounts","type":"uint256[4]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"totalFee","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLidoLocator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"canDeposit","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDepositableEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalELRewardsCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"maxStakeLimit","type":"uint256"},{"indexed":false,"name":"stakeLimitIncreasePerBlock","type":"uint256"}],"name":"StakingLimitSet","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingLimitRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLValidators","type":"uint256"},{"indexed":false,"name":"postCLValidators","type":"uint256"}],"name":"CLValidatorsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"depositedValidators","type":"uint256"}],"name":"DepositedValidatorsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLBalance","type":"uint256"},{"indexed":false,"name":"postCLBalance","type":"uint256"},{"indexed":false,"name":"withdrawalsWithdrawn","type":"uint256"},{"indexed":false,"name":"executionLayerRewardsWithdrawn","type":"uint256"},{"indexed":false,"name":"postBufferedEther","type":"uint256"}],"name":"ETHDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"timeElapsed","type":"uint256"},{"indexed":false,"name":"preTotalShares","type":"uint256"},{"indexed":false,"name":"preTotalEther","type":"uint256"},{"indexed":false,"name":"postTotalShares","type":"uint256"},{"indexed":false,"name":"postTotalEther","type":"uint256"},{"indexed":false,"name":"sharesMintedAsFees","type":"uint256"}],"name":"TokenRebased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"lidoLocator","type":"address"}],"name":"LidoLocatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"ELRewardsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"WithdrawalsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"eip712StETH","type":"address"}],"name":"EIP712StETHInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"preRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"postRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"}] \ No newline at end of file +[ + { + "constant": true, + "inputs": [ + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "getPooledEthBySharesRoundUp", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resume", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "mintExternalShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "stop", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "hasInitialized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKING_CONTROL_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_ethAmount", + "type": "uint256" + } + ], + "name": "getSharesByPooledEth", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_timeElapsed", + "type": "uint256" + }, + { + "name": "_preTotalShares", + "type": "uint256" + }, + { + "name": "_preTotalEther", + "type": "uint256" + }, + { + "name": "_postTotalShares", + "type": "uint256" + }, + { + "name": "_postTotalEther", + "type": "uint256" + }, + { + "name": "_postInternalShares", + "type": "uint256" + }, + { + "name": "_postInternalEther", + "type": "uint256" + }, + { + "name": "_sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "emitTokenRebase", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isStakingPaused", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_script", + "type": "bytes" + } + ], + "name": "getEVMScriptExecutor", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxStakeLimit", + "type": "uint256" + }, + { + "name": "_stakeLimitIncreasePerBlock", + "type": "uint256" + } + ], + "name": "setStakingLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getRecoveryVault", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalPooledEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newDepositedValidators", + "type": "uint256" + } + ], + "name": "unsafeChangeDepositedValidators", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTreasury", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isStopped", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBufferedEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_lidoLocator", + "type": "address" + }, + { + "name": "_eip712StETH", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "receiveELRewards", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "mintShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getWithdrawalCredentials", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_preClValidators", + "type": "uint256" + }, + { + "name": "_reportClValidators", + "type": "uint256" + }, + { + "name": "_reportClBalance", + "type": "uint256" + } + ], + "name": "processClStateUpdate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentStakeLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getExternalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "internalizeExternalBadDebt", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getStakeLimitFullInfo", + "outputs": [ + { + "name": "isStakingPaused_", + "type": "bool" + }, + { + "name": "isStakingLimitSet", + "type": "bool" + }, + { + "name": "currentStakeLimit", + "type": "uint256" + }, + { + "name": "maxStakeLimit", + "type": "uint256" + }, + { + "name": "maxStakeLimitGrowthBlocks", + "type": "uint256" + }, + { + "name": "prevStakeLimit", + "type": "uint256" + }, + { + "name": "prevStakeBlockNumber", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "transferSharesFrom", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "burnExternalShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resumeStaking", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFeeDistribution", + "outputs": [ + { + "name": "treasuryFeeBasisPoints", + "type": "uint16" + }, + { + "name": "insuranceFeeBasisPoints", + "type": "uint16" + }, + { + "name": "operatorsFeeBasisPoints", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "receiveWithdrawals", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "getPooledEthByShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "rebalanceExternalEtherToInternal", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "token", + "type": "address" + } + ], + "name": "allowRecoverability", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "appId", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "burnShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_oldBurner", + "type": "address" + }, + { + "name": "_contractsWithBurnerAllowances", + "type": "address[]" + }, + { + "name": "_initialMaxExternalRatioBP", + "type": "uint256" + } + ], + "name": "finalizeUpgrade_v3", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getMaxMintableExternalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getContractVersion", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getInitializationBlock", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "transferShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_reportClBalance", + "type": "uint256" + }, + { + "name": "_principalCLBalance", + "type": "uint256" + }, + { + "name": "_withdrawalsToWithdraw", + "type": "uint256" + }, + { + "name": "_elRewardsToWithdraw", + "type": "uint256" + }, + { + "name": "_lastWithdrawalRequestToFinalize", + "type": "uint256" + }, + { + "name": "_withdrawalsShareRate", + "type": "uint256" + }, + { + "name": "_etherToLockOnWithdrawalQueue", + "type": "uint256" + } + ], + "name": "collectRewardsAndProcessWithdrawals", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEIP712StETH", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getMaxExternalRatioBP", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "transferToVault", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + }, + { + "name": "_params", + "type": "uint256[]" + } + ], + "name": "canPerform", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_referral", + "type": "address" + } + ], + "name": "submit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEVMScriptRegistry", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxDepositsCount", + "type": "uint256" + }, + { + "name": "_stakingModuleId", + "type": "uint256" + }, + { + "name": "_depositCalldata", + "type": "bytes" + } + ], + "name": "deposit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBeaconStat", + "outputs": [ + { + "name": "depositedValidators", + "type": "uint256" + }, + { + "name": "beaconValidators", + "type": "uint256" + }, + { + "name": "beaconBalance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "removeStakingLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFee", + "outputs": [ + { + "name": "totalFee", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kernel", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_deadline", + "type": "uint256" + }, + { + "name": "_v", + "type": "uint8" + }, + { + "name": "_r", + "type": "bytes32" + }, + { + "name": "_s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isPetrified", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getExternalEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getLidoLocator", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "canDeposit", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKING_PAUSE_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getDepositableEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxExternalRatioBP", + "type": "uint256" + } + ], + "name": "setMaxExternalRatioBP", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_account", + "type": "address" + } + ], + "name": "sharesOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "pauseStaking", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalELRewardsCollected", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingResumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "maxStakeLimit", + "type": "uint256" + }, + { + "indexed": false, + "name": "stakeLimitIncreasePerBlock", + "type": "uint256" + } + ], + "name": "StakingLimitSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingLimitRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "preCLValidators", + "type": "uint256" + }, + { + "indexed": false, + "name": "postCLValidators", + "type": "uint256" + } + ], + "name": "CLValidatorsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "depositedValidators", + "type": "uint256" + } + ], + "name": "DepositedValidatorsChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "preCLBalance", + "type": "uint256" + }, + { + "indexed": false, + "name": "postCLBalance", + "type": "uint256" + }, + { + "indexed": false, + "name": "withdrawalsWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "name": "executionLayerRewardsWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "name": "postBufferedEther", + "type": "uint256" + } + ], + "name": "ETHDistributed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "timeElapsed", + "type": "uint256" + }, + { + "indexed": false, + "name": "preTotalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "preTotalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "postTotalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "postTotalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "TokenRebased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "lidoLocator", + "type": "address" + } + ], + "name": "LidoLocatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "ELRewardsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawalsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "name": "referral", + "type": "address" + } + ], + "name": "Submitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Unbuffered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "postInternalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "postInternalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "InternalShareRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalSharesMinted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalSharesBurnt", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "maxExternalRatioBP", + "type": "uint256" + } + ], + "name": "MaxExternalRatioBPSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "ExternalEtherTransferredToBuffer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalBadDebtInternalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "executor", + "type": "address" + }, + { + "indexed": false, + "name": "script", + "type": "bytes" + }, + { + "indexed": false, + "name": "input", + "type": "bytes" + }, + { + "indexed": false, + "name": "returnData", + "type": "bytes" + } + ], + "name": "ScriptResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "RecoverToVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "eip712StETH", + "type": "address" + } + ], + "name": "EIP712StETHInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "sharesValue", + "type": "uint256" + } + ], + "name": "TransferShares", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "account", + "type": "address" + }, + { + "indexed": false, + "name": "preRebaseTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "postRebaseTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesAmount", + "type": "uint256" + } + ], + "name": "SharesBurnt", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Stopped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "version", + "type": "uint256" + } + ], + "name": "ContractVersionSet", + "type": "event" + } + ] diff --git a/scripts/upgrade_2025_11_24_mainnet_v3.py b/scripts/upgrade_2025_11_24_mainnet_v3.py index 7c9e424e3..41192cf4e 100644 --- a/scripts/upgrade_2025_11_24_mainnet_v3.py +++ b/scripts/upgrade_2025_11_24_mainnet_v3.py @@ -1,59 +1,28 @@ """ -# Vote 2025_11_24 +# Vote 2025_12_10 === 1. DG PROPOPSAL === -I. Decrease Easy Track TRP limit -1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO -1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - -II. Increase SDVT target share -1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +I. Lido V3 upgrade - stVaults === NON-DG ITEMS === -III. Transfer MATIC from Lido Treasury to Lido Labs Foundation -2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F +II. Lido V3 ET factories rights # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ from typing import Dict, List, Tuple -from utils.finance import make_matic_payout 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 get_deployer_account, get_is_live, get_priority_fee from utils.mainnet_fork import pass_and_exec_dao_vote from utils.dual_governance import submit_proposals -from utils.agent import agent_forward from brownie import interface -from utils.allowed_recipients_registry import ( - unsafe_set_spent_amount, - set_limit_parameters, -) # ============================== Addresses =================================== -ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" -STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -LIDO_LABS_MS = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" - OMNIBUS_CONTRACT = "0xA3710716965497e62bC3165Eb7DD2a1B1437f8Af" # TODO replace with the actual omnibus contract address -# ============================== Constants =================================== -SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 -SDVT_MODULE_MODULE_FEE_BP = 800 -SDVT_MODULE_TREASURY_FEE_BP = 200 -SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 -SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 - -TRP_PERIOD_DURATION_MONTHS = 12 -TRP_NEW_LIMIT = 15_000_000 * 10**18 -TRP_NEW_SPENT_AMOUNT = 0 - -MATIC_FOR_TRANSFER = 508_106 * 10**18 - # ============================= Description ================================== # TODO diff --git a/tests/acceptance/test_lido.py b/tests/acceptance/test_lido.py index 0ac485621..c476c2fb3 100644 --- a/tests/acceptance/test_lido.py +++ b/tests/acceptance/test_lido.py @@ -45,7 +45,7 @@ def test_pausable(contract): def test_versioned(contract): - assert contract.getContractVersion() == 2 + assert contract.getContractVersion() == 3 def test_initialize(contract): @@ -55,7 +55,7 @@ def test_initialize(contract): def test_finalize_upgrade(contract): with reverts("UNEXPECTED_CONTRACT_VERSION"): - contract.finalizeUpgrade_v2(contracts.lido_locator, contracts.eip712_steth, {"from": contracts.voting}) + contract.finalizeUpgrade_v3(contracts.burner, [contracts.eip712_steth], 0, {"from": contracts.voting}) def test_petrified(): @@ -63,8 +63,8 @@ def test_petrified(): with reverts("INIT_ALREADY_INITIALIZED"): impl.initialize(contracts.lido_locator, contracts.eip712_steth, {"from": contracts.voting}) - with reverts("UNEXPECTED_CONTRACT_VERSION"): - impl.finalizeUpgrade_v2(contracts.lido_locator, contracts.eip712_steth, {"from": contracts.voting}) + with reverts("NOT_INITIALIZED"): # TODO: UNEXPECTED_CONTRACT_VERSION + impl.finalizeUpgrade_v3(contracts.burner, [contracts.eip712_steth], 0, {"from": contracts.voting}) def test_links(contract): @@ -92,7 +92,7 @@ def test_lido_state(contract): for module in modules ) - assert stake_limit["isStakingPaused"] == False + assert stake_limit["isStakingPaused_"] == False assert stake_limit["isStakingLimitSet"] == True assert stake_limit["maxStakeLimit"] == LIDO_MAX_STAKE_LIMIT_ETH * ONE_ETH From e5dd742abf4f4850baef7c01b8cf7123368c1731 Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 13 Nov 2025 20:03:21 +0300 Subject: [PATCH 031/178] test: fix accounting oracle tests --- configs/config_mainnet.py | 4 +- interfaces/AccountingOracle.json | 85 +++------------------- tests/acceptance/test_accounting_oracle.py | 25 ++----- 3 files changed, 19 insertions(+), 95 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index f445b9921..2a0b5d383 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -239,11 +239,11 @@ # AccountingOracle # and its corresponding HashConsensus ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" -ACCOUNTING_ORACLE_IMPL = "0xE9906E543274cebcd335d2C560094089e9547e8d" +ACCOUNTING_ORACLE_IMPL = "0x6cC54EF252B498B0Efb0b6d450a09EE5833104b2" # TODO fix after mainnet deployment HASH_CONSENSUS_FOR_AO = "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288" AO_EPOCHS_PER_FRAME = 225 AO_FAST_LANE_LENGTH_SLOTS = 100 -AO_CONSENSUS_VERSION = 4 +AO_CONSENSUS_VERSION = 5 # ValidatorsExitBusOracle VALIDATORS_EXIT_BUS_ORACLE = "0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e" diff --git a/interfaces/AccountingOracle.json b/interfaces/AccountingOracle.json index 35c6e7e9f..73e7ef341 100644 --- a/interfaces/AccountingOracle.json +++ b/interfaces/AccountingOracle.json @@ -6,16 +6,6 @@ "name": "lidoLocator", "type": "address" }, - { - "internalType": "address", - "name": "lido", - "type": "address" - }, - { - "internalType": "address", - "name": "legacyOracle", - "type": "address" - }, { "internalType": "uint256", "name": "secondsPerSlot", @@ -145,11 +135,6 @@ "name": "InvalidExtraDataSortOrder", "type": "error" }, - { - "inputs": [], - "name": "LegacyOracleCannotBeZero", - "type": "error" - }, { "inputs": [], "name": "LidoCannotBeZero", @@ -728,32 +713,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "LEGACY_ORACLE", - "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": "LOCATOR", @@ -840,14 +799,7 @@ "type": "uint256" } ], - "name": "finalizeUpgrade_v2", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "finalizeUpgrade_v3", + "name": "finalizeUpgrade_v4", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1096,29 +1048,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "admin", - "type": "address" - }, - { - "internalType": "address", - "name": "consensusContract", - "type": "address" - }, - { - "internalType": "uint256", - "name": "consensusVersion", - "type": "uint256" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -1142,7 +1071,7 @@ "type": "uint256" } ], - "name": "initializeWithoutMigration", + "name": "initialize", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1296,6 +1225,16 @@ "name": "isBunkerMode", "type": "bool" }, + { + "internalType": "bytes32", + "name": "vaultsDataTreeRoot", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "vaultsDataTreeCid", + "type": "string" + }, { "internalType": "uint256", "name": "extraDataFormat", diff --git a/tests/acceptance/test_accounting_oracle.py b/tests/acceptance/test_accounting_oracle.py index 151c04d0b..a0459ee6e 100644 --- a/tests/acceptance/test_accounting_oracle.py +++ b/tests/acceptance/test_accounting_oracle.py @@ -29,7 +29,6 @@ def test_proxy(contract): def test_constants(contract): - assert contract.LIDO() == contracts.lido assert contract.LOCATOR() == contracts.lido_locator assert contract.EXTRA_DATA_FORMAT_EMPTY() == 0 assert contract.EXTRA_DATA_FORMAT_LIST() == 1 @@ -40,22 +39,15 @@ def test_constants(contract): def test_versioned(contract): - assert contract.getContractVersion() == 3 + assert contract.getContractVersion() == 4 def test_initialize(contract): - with reverts(encode_error("IncorrectOracleMigration(uint256)", [2])): + with reverts("NonZeroContractVersionOnInit: "): contract.initialize( contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), HASH_CONSENSUS_FOR_AO, - 1, - {"from": contracts.voting}, - ) - with reverts(encode_error("NonZeroContractVersionOnInit()")): - contract.initializeWithoutMigration( - contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), - HASH_CONSENSUS_FOR_AO, - 1, + AO_CONSENSUS_VERSION, 1, {"from": contracts.voting}, ) @@ -63,18 +55,11 @@ def test_initialize(contract): def test_petrified(contract): impl = interface.AccountingOracle(ACCOUNTING_ORACLE_IMPL) - with reverts(encode_error("IncorrectOracleMigration(uint256)", [2])): + with reverts("NonZeroContractVersionOnInit: "): impl.initialize( contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), HASH_CONSENSUS_FOR_AO, - 1, - {"from": contracts.voting}, - ) - with reverts(encode_error("NonZeroContractVersionOnInit()")): - impl.initializeWithoutMigration( - contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), - HASH_CONSENSUS_FOR_AO, - 1, + AO_CONSENSUS_VERSION, 1, {"from": contracts.voting}, ) From ef5489e58d555b74713ce2b12da29ae2c0b83e41 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 14 Nov 2025 11:35:40 +0300 Subject: [PATCH 032/178] Update TRP_ALREADY_SPENT_BEFORE value in test --- tests/test_2025_11_24.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_2025_11_24.py b/tests/test_2025_11_24.py index 443c4844c..da60aac52 100644 --- a/tests/test_2025_11_24.py +++ b/tests/test_2025_11_24.py @@ -85,7 +85,7 @@ MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 -TRP_ALREADY_SPENT_BEFORE = 2_676_801 * 10**18 +TRP_ALREADY_SPENT_BEFORE = 2_708_709 * 10**18 TRP_ALREADY_SPENT_AFTER = 0 TRP_LIMIT_AFTER = 15_000_000 * 10**18 TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC From 9a4c2ed14442420238290502e62af7f90fd4284a Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 14 Nov 2025 08:19:44 +0000 Subject: [PATCH 033/178] feat: make emitted by checks mandatory --- .../allowed_recipients_registry.py | 20 +++--- .../allowed_tokens_registry.py | 9 ++- utils/test/event_validators/csm.py | 9 ++- .../test/event_validators/dual_governance.py | 11 ++- utils/test/event_validators/easy_track.py | 30 ++++---- utils/test/event_validators/hash_consensus.py | 20 +++--- .../node_operators_registry.py | 14 ++-- .../oracle_report_sanity_checker.py | 30 ++++---- utils/test/event_validators/payout.py | 7 +- utils/test/event_validators/permission.py | 69 +++++++++---------- utils/test/event_validators/proxy.py | 7 +- .../event_validators/relay_allowed_list.py | 10 +-- .../test/event_validators/rewards_manager.py | 8 +-- utils/test/event_validators/staking_router.py | 7 +- .../test/event_validators/time_constraints.py | 21 +++--- 15 files changed, 129 insertions(+), 143 deletions(-) diff --git a/utils/test/event_validators/allowed_recipients_registry.py b/utils/test/event_validators/allowed_recipients_registry.py index 4af93784b..89bcbfcb1 100644 --- a/utils/test/event_validators/allowed_recipients_registry.py +++ b/utils/test/event_validators/allowed_recipients_registry.py @@ -24,11 +24,11 @@ def validate_set_limit_parameter_event( assert event.count("LimitsParametersChanged") == 1 assert event["LimitsParametersChanged"]["_limit"] == limit assert event["LimitsParametersChanged"]["_periodDurationMonths"] == period_duration_month - if emitted_by is not None: - event_emitted_by = convert.to_address(event["LimitsParametersChanged"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["LimitsParametersChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_update_spent_amount_event( @@ -73,8 +73,8 @@ def validate_set_spent_amount_event( assert event.count("SpentAmountChanged") == 1 assert event["SpentAmountChanged"]["_newSpentAmount"] == new_spent_amount - if emitted_by is not None: - event_emitted_by = convert.to_address(event["SpentAmountChanged"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["SpentAmountChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/allowed_tokens_registry.py b/utils/test/event_validators/allowed_tokens_registry.py index 1430ad972..55750b3bb 100644 --- a/utils/test/event_validators/allowed_tokens_registry.py +++ b/utils/test/event_validators/allowed_tokens_registry.py @@ -19,8 +19,7 @@ def validate_add_token_event( token ), f"Wrong token address {event['TokenAdded']['_token']} but expected {token}" - if emitted_by is not None: - event_emitted_by = convert.to_address(event["TokenAdded"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + event_emitted_by = convert.to_address(event["TokenAdded"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/csm.py b/utils/test/event_validators/csm.py index 38549b311..29805ccb1 100644 --- a/utils/test/event_validators/csm.py +++ b/utils/test/event_validators/csm.py @@ -24,8 +24,7 @@ def validate_set_key_removal_charge_event( assert event.count("KeyRemovalChargeSet") == 1 assert event["KeyRemovalChargeSet"]["amount"] == key_removal_charge - if emitted_by is not None: - event_emitted_by = convert.to_address(event["KeyRemovalChargeSet"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + event_emitted_by = convert.to_address(event["KeyRemovalChargeSet"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/dual_governance.py b/utils/test/event_validators/dual_governance.py index 9aae57aae..055fc63dd 100644 --- a/utils/test/event_validators/dual_governance.py +++ b/utils/test/event_validators/dual_governance.py @@ -33,12 +33,11 @@ def validate_dual_governance_submit_event( if metadata: assert event["ProposalSubmitted"][1]["metadata"] == metadata, f"Wrong metadata {event['ProposalSubmitted'][1]['metadata']}" - if emitted_by is not None: - assert len(event["ProposalSubmitted"]) == len(emitted_by), "Wrong emitted_by count" - for i in range(0, len(emitted_by)): - assert convert.to_address(event["ProposalSubmitted"][i]["_emitted_by"]) == convert.to_address( - emitted_by[i] - ), "Wrong event emitter" + assert len(event["ProposalSubmitted"]) == len(emitted_by), "Wrong emitted_by count" + for i in range(0, len(emitted_by)): + assert convert.to_address(event["ProposalSubmitted"][i]["_emitted_by"]) == convert.to_address( + emitted_by[i] + ), "Wrong event emitter" def validate_dual_governance_tiebreaker_activation_timeout_set_event( diff --git a/utils/test/event_validators/easy_track.py b/utils/test/event_validators/easy_track.py index 2216ec890..d9d0b6ce0 100644 --- a/utils/test/event_validators/easy_track.py +++ b/utils/test/event_validators/easy_track.py @@ -22,11 +22,11 @@ def validate_evmscript_factory_added_event( assert event["EVMScriptFactoryAdded"]["_evmScriptFactory"] == p.factory_addr assert event["EVMScriptFactoryAdded"]["_permissions"] == p.permissions - if emitted_by is not None: - event_emitted_by = convert.to_address(event["EVMScriptFactoryAdded"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["EVMScriptFactoryAdded"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_evmscript_factory_removed_event(event: EventDict, factory_addr: str, emitted_by: str | None = None): @@ -37,11 +37,11 @@ def validate_evmscript_factory_removed_event(event: EventDict, factory_addr: str assert event.count("EVMScriptFactoryRemoved") == 1 assert event["EVMScriptFactoryRemoved"]["_evmScriptFactory"] == factory_addr - if emitted_by is not None: - event_emitted_by = convert.to_address(event["EVMScriptFactoryRemoved"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["EVMScriptFactoryRemoved"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_motions_count_limit_changed_event( @@ -54,8 +54,8 @@ def validate_motions_count_limit_changed_event( assert event.count("MotionsCountLimitChanged") == 1 assert event["MotionsCountLimitChanged"]["_newMotionsCountLimit"] == motions_count_limit - if emitted_by is not None: - event_emitted_by = convert.to_address(event["MotionsCountLimitChanged"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["MotionsCountLimitChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/hash_consensus.py b/utils/test/event_validators/hash_consensus.py index 93c897f2d..084ef009d 100644 --- a/utils/test/event_validators/hash_consensus.py +++ b/utils/test/event_validators/hash_consensus.py @@ -17,11 +17,11 @@ def validate_hash_consensus_member_removed(event: EventDict, member: str, new_qu assert event["MemberRemoved"]["addr"] == member assert event["MemberRemoved"]["newQuorum"] == new_quorum assert event["MemberRemoved"]["newTotalMembers"] == new_total_members - if emitted_by is not None: - event_emitted_by = convert.to_address(event["MemberRemoved"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["MemberRemoved"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_hash_consensus_member_added(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None, is_dg_event: bool = False): if is_dg_event: @@ -36,8 +36,8 @@ def validate_hash_consensus_member_added(event: EventDict, member: str, new_quor assert event["MemberAdded"]["addr"] == member assert event["MemberAdded"]["newQuorum"] == new_quorum assert event["MemberAdded"]["newTotalMembers"] == new_total_members - if emitted_by is not None: - event_emitted_by = convert.to_address(event["MemberAdded"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["MemberAdded"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/node_operators_registry.py b/utils/test/event_validators/node_operators_registry.py index a8a81896c..2ca183edb 100644 --- a/utils/test/event_validators/node_operators_registry.py +++ b/utils/test/event_validators/node_operators_registry.py @@ -74,10 +74,9 @@ def validate_node_operator_name_set_event( assert event["NodeOperatorNameSet"]["nodeOperatorId"] == node_operator_name_item.nodeOperatorId assert event["NodeOperatorNameSet"]["name"] == node_operator_name_item.name - if emitted_by is not None: - assert convert.to_address(event["NodeOperatorNameSet"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["NodeOperatorNameSet"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_node_operator_reward_address_set_event( event: EventDict, node_operator_reward_address_item: NodeOperatorRewardAddressSetItem, emitted_by: str = None, is_dg_event=False @@ -94,10 +93,9 @@ def validate_node_operator_reward_address_set_event( assert event["NodeOperatorRewardAddressSet"]["nodeOperatorId"] == node_operator_reward_address_item.nodeOperatorId assert event["NodeOperatorRewardAddressSet"]["rewardAddress"] == node_operator_reward_address_item.reward_address - if emitted_by is not None: - assert convert.to_address(event["NodeOperatorRewardAddressSet"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["NodeOperatorRewardAddressSet"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_target_validators_count_changed_event(event: EventDict, t: TargetValidatorsCountChanged): _events_chain = ["LogScriptCall", "LogScriptCall", "TargetValidatorsCountChanged", "KeysOpIndexSet", "NonceChanged", "ScriptResult"] diff --git a/utils/test/event_validators/oracle_report_sanity_checker.py b/utils/test/event_validators/oracle_report_sanity_checker.py index 2989bc407..75aa4875a 100644 --- a/utils/test/event_validators/oracle_report_sanity_checker.py +++ b/utils/test/event_validators/oracle_report_sanity_checker.py @@ -15,11 +15,11 @@ def validate_exited_validators_per_day_limit_event(event: EventDict, value: int, assert event.count("ExitedValidatorsPerDayLimitSet") == 1 assert event["ExitedValidatorsPerDayLimitSet"]["exitedValidatorsPerDayLimit"] == value - if emitted_by is not None: - event_emitted_by = convert.to_address(event["ExitedValidatorsPerDayLimitSet"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["ExitedValidatorsPerDayLimitSet"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_appeared_validators_limit_event(event: EventDict, value: int, emitted_by: str | None = None): _events_chain = [ @@ -34,11 +34,11 @@ def validate_appeared_validators_limit_event(event: EventDict, value: int, emitt assert event.count("AppearedValidatorsPerDayLimitSet") == 1 assert event["AppearedValidatorsPerDayLimitSet"]["appearedValidatorsPerDayLimit"] == value - if emitted_by is not None: - event_emitted_by = convert.to_address(event["AppearedValidatorsPerDayLimitSet"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["AppearedValidatorsPerDayLimitSet"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_initial_slashing_and_penalties_event(event: EventDict, value: int, emitted_by: str | None = None): _events_chain = [ @@ -53,8 +53,8 @@ def validate_initial_slashing_and_penalties_event(event: EventDict, value: int, assert event.count("InitialSlashingAmountSet") == 1 assert event["InitialSlashingAmountSet"]["initialSlashingAmountPWei"] == value - if emitted_by is not None: - event_emitted_by = convert.to_address(event["InitialSlashingAmountSet"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" \ No newline at end of file + + event_emitted_by = convert.to_address(event["InitialSlashingAmountSet"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" \ No newline at end of file diff --git a/utils/test/event_validators/payout.py b/utils/test/event_validators/payout.py index e42ba274a..07e8c5ad5 100644 --- a/utils/test/event_validators/payout.py +++ b/utils/test/event_validators/payout.py @@ -50,10 +50,9 @@ def validate_token_payout_event(event: EventDict, p: Payout, is_steth: bool = Fa assert event["NewTransaction"]["entity"] == p.to_addr assert event["NewTransaction"]["amount"] == p.amount - if emitted_by is not None: - assert convert.to_address(event["VaultTransfer"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["VaultTransfer"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_ether_payout_event(event: EventDict, p: Payout): diff --git a/utils/test/event_validators/permission.py b/utils/test/event_validators/permission.py index 125f7c5c8..221e2de59 100644 --- a/utils/test/event_validators/permission.py +++ b/utils/test/event_validators/permission.py @@ -39,13 +39,12 @@ def validate_permission_create_event(event: EventDict, p: Permission, manager: s assert event["ChangePermissionManager"]["role"] == p.role, "Wrong role" assert event["ChangePermissionManager"]["manager"] == manager, "Wrong manager" - if emitted_by is not None: - assert convert.to_address(event["ChangePermissionManager"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["ChangePermissionManager"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_permission_revoke_event(event: EventDict, p: Permission, emitted_by: str = None, granted_from_agent: bool = False) -> None: @@ -63,10 +62,10 @@ def validate_permission_revoke_event(event: EventDict, p: Permission, emitted_by assert event["SetPermission"]["app"] == p.app, "Wrong app address" assert event["SetPermission"]["role"] == p.role, "Wrong role" assert event["SetPermission"]["allowed"] is False, "Wrong role" - if emitted_by is not None: - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_permission_grant_event(event: EventDict, p: Permission, emitted_by: str = None, granted_from_agent: bool = False) -> None: @@ -83,10 +82,10 @@ def validate_permission_grant_event(event: EventDict, p: Permission, emitted_by: assert event["SetPermission"]["app"] == p.app, "Wrong app address" assert event["SetPermission"]["role"] == p.role, "Wrong role" assert event["SetPermission"]["allowed"] is True, "Wrong allowed flag" - if emitted_by is not None: - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_permission_grantp_event(event: EventDict, p: Permission, params: List[Param], emitted_by: str = None) -> None: @@ -108,13 +107,12 @@ def validate_permission_grantp_event(event: EventDict, p: Permission, params: Li assert event["SetPermissionParams"]["role"] == p.role, "Wrong role" assert event["SetPermissionParams"]["paramsHash"] == params_hash - if emitted_by is not None: - assert convert.to_address(event["SetPermissionParams"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["SetPermissionParams"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_grant_role_event(events: EventDict, role: str, grant_to: str, sender: str, emitted_by: str = None, is_dg_event: bool = False) -> None: @@ -132,10 +130,10 @@ def validate_grant_role_event(events: EventDict, role: str, grant_to: str, sende assert events["RoleGranted"]["role"] == role, "Wrong role" assert events["RoleGranted"]["account"] == grant_to, "Wrong account" assert events["RoleGranted"]["sender"] == sender, "Wrong sender" - if emitted_by is not None: - assert convert.to_address(events["RoleGranted"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(events["RoleGranted"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_revoke_role_event( events: EventDict, role: str, revoke_from: str, sender: str, emitted_by: str = None, is_dg_event: bool = False @@ -154,8 +152,7 @@ def validate_revoke_role_event( assert events["RoleRevoked"]["account"] == revoke_from, "Wrong account" assert events["RoleRevoked"]["sender"] == sender, "Wrong sender" - if emitted_by is not None: - assert convert.to_address(events["RoleRevoked"]["_emitted_by"]) == convert.to_address(emitted_by), "Wrong event emitter" + assert convert.to_address(events["RoleRevoked"]["_emitted_by"]) == convert.to_address(emitted_by), "Wrong event emitter" def validate_set_permission_manager_event(event: EventDict, app: str, role: str, manager: str, emitted_by: str = None) -> None: @@ -169,10 +166,10 @@ def validate_set_permission_manager_event(event: EventDict, app: str, role: str, assert event["ChangePermissionManager"]["app"] == app, "Wrong app address" assert event["ChangePermissionManager"]["role"] == role, "Wrong role" assert event["ChangePermissionManager"]["manager"] == manager, "Wrong manager" - if emitted_by is not None: - assert convert.to_address(event["ChangePermissionManager"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["ChangePermissionManager"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_dg_permission_revoke_event(event: EventDict, p: Permission, emitted_by: str = None) -> None: @@ -189,7 +186,7 @@ def validate_dg_permission_revoke_event(event: EventDict, p: Permission, emitted assert event["SetPermission"]["app"] == p.app, "Wrong app address" assert event["SetPermission"]["role"] == p.role, "Wrong role" assert event["SetPermission"]["allowed"] is False, "Wrong role" - if emitted_by is not None: - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" diff --git a/utils/test/event_validators/proxy.py b/utils/test/event_validators/proxy.py index b7c19dcc6..f1241fa9b 100644 --- a/utils/test/event_validators/proxy.py +++ b/utils/test/event_validators/proxy.py @@ -14,7 +14,6 @@ def validate_proxy_admin_changed(event: EventDict, prev_admin: str, new_admin: s assert event["AdminChanged"]["previousAdmin"] == prev_admin, "Wrong previous admin" assert event["AdminChanged"]["newAdmin"] == new_admin, "Wrong new admin" - if emitted_by is not None: - assert convert.to_address(event["AdminChanged"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["AdminChanged"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" diff --git a/utils/test/event_validators/relay_allowed_list.py b/utils/test/event_validators/relay_allowed_list.py index f068daadf..676baaa0d 100644 --- a/utils/test/event_validators/relay_allowed_list.py +++ b/utils/test/event_validators/relay_allowed_list.py @@ -11,8 +11,8 @@ def validate_relay_allowed_list_manager_set(event: EventDict, new_manager: str, assert event.count("ManagerChanged") == 1 assert event["ManagerChanged"]["new_manager"] == new_manager - if emitted_by is not None: - event_emitted_by = convert.to_address(event["ManagerChanged"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["ManagerChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/rewards_manager.py b/utils/test/event_validators/rewards_manager.py index e28389b8b..84a2dd216 100644 --- a/utils/test/event_validators/rewards_manager.py +++ b/utils/test/event_validators/rewards_manager.py @@ -17,7 +17,7 @@ def validate_ownership_transferred_event(event: EventDict, ot: OwnershipTransfer assert event['OwnershipTransferred']['previousOwner'] == ot.previous_owner_addr assert event['OwnershipTransferred']['newOwner'] == ot.new_owner_addr - if emitted_by is not None: - assert convert.to_address(event["OwnershipTransferred"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["OwnershipTransferred"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" diff --git a/utils/test/event_validators/staking_router.py b/utils/test/event_validators/staking_router.py index b378f19a4..6d31471c4 100644 --- a/utils/test/event_validators/staking_router.py +++ b/utils/test/event_validators/staking_router.py @@ -86,7 +86,6 @@ def validate_staking_module_update_event(event: EventDict, module_item: StakingM assert event["StakingModuleFeesSet"]["stakingModuleFee"] == module_item.module_fee assert event["StakingModuleFeesSet"]["treasuryFee"] == module_item.treasury_fee - if emitted_by is not None: - assert convert.to_address(event["StakingModuleFeesSet"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["StakingModuleFeesSet"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" diff --git a/utils/test/event_validators/time_constraints.py b/utils/test/event_validators/time_constraints.py index 346933126..c21b78afb 100644 --- a/utils/test/event_validators/time_constraints.py +++ b/utils/test/event_validators/time_constraints.py @@ -12,10 +12,9 @@ def validate_time_constraints_executed_before_event(event: EventDict, timestamp, assert event.count("TimeBeforeTimestampChecked") == 1 assert event["TimeBeforeTimestampChecked"][0]["timestamp"] == timestamp - if emitted_by is not None: - assert convert.to_address(event["TimeBeforeTimestampChecked"][0]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["TimeBeforeTimestampChecked"][0]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_dg_time_constraints_executed_before_event(event: EventDict, timestamp, emitted_by: str = None) -> None: @@ -26,10 +25,9 @@ def validate_dg_time_constraints_executed_before_event(event: EventDict, timesta assert event.count("TimeBeforeTimestampChecked") == 1 assert event["TimeBeforeTimestampChecked"][0]["timestamp"] == timestamp - if emitted_by is not None: - assert convert.to_address(event["TimeBeforeTimestampChecked"][0]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["TimeBeforeTimestampChecked"][0]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_dg_time_constraints_executed_within_day_time_event(event: EventDict, start_day_time, end_day_time, emitted_by: str = None) -> None: @@ -41,7 +39,6 @@ def validate_dg_time_constraints_executed_within_day_time_event(event: EventDict assert event["TimeWithinDayTimeChecked"][0]["startDayTime"] == start_day_time assert event["TimeWithinDayTimeChecked"][0]["endDayTime"] == end_day_time - if emitted_by is not None: - assert convert.to_address(event["TimeWithinDayTimeChecked"][0]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["TimeWithinDayTimeChecked"][0]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" From 431ef583193715a44ce398660c76535f8df58c2f Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 14 Nov 2025 13:25:13 +0000 Subject: [PATCH 034/178] do not pass DG event emitters --- tests/test_2025_11_24.py | 1 - utils/test/event_validators/dual_governance.py | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_2025_11_24.py b/tests/test_2025_11_24.py index da60aac52..2e20dd8fd 100644 --- a/tests/test_2025_11_24.py +++ b/tests/test_2025_11_24.py @@ -223,7 +223,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, metadata="Upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit", proposal_calls=dual_governance_proposal_calls, - emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], ) # TODO validate all other voting events diff --git a/utils/test/event_validators/dual_governance.py b/utils/test/event_validators/dual_governance.py index 055fc63dd..922fb93c5 100644 --- a/utils/test/event_validators/dual_governance.py +++ b/utils/test/event_validators/dual_governance.py @@ -1,6 +1,7 @@ from brownie.network.event import EventDict from brownie import convert, web3 from .common import validate_events_chain +from utils.config import DUAL_GOVERNANCE, TIMELOCK def validate_dual_governance_submit_event( @@ -10,7 +11,6 @@ def validate_dual_governance_submit_event( executor: str, metadata: str = None, proposal_calls: any = None, - emitted_by: list[str] = None, ) -> None: _events_chain = ["LogScriptCall", "ProposalSubmitted", "ProposalSubmitted"] @@ -33,11 +33,13 @@ def validate_dual_governance_submit_event( if metadata: assert event["ProposalSubmitted"][1]["metadata"] == metadata, f"Wrong metadata {event['ProposalSubmitted'][1]['metadata']}" - assert len(event["ProposalSubmitted"]) == len(emitted_by), "Wrong emitted_by count" - for i in range(0, len(emitted_by)): - assert convert.to_address(event["ProposalSubmitted"][i]["_emitted_by"]) == convert.to_address( - emitted_by[i] - ), "Wrong event emitter" + assert len(event["ProposalSubmitted"]) == 2, "Wrong emitted_by count" + assert convert.to_address(event["ProposalSubmitted"][0]["_emitted_by"]) == convert.to_address( + TIMELOCK + ), "Wrong event emitter" + assert convert.to_address(event["ProposalSubmitted"][1]["_emitted_by"]) == convert.to_address( + DUAL_GOVERNANCE + ), "Wrong event emitter" def validate_dual_governance_tiebreaker_activation_timeout_set_event( From d0fef51f0f4039257f179f1d85a37878c430de26 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 14 Nov 2025 13:59:58 +0000 Subject: [PATCH 035/178] do not pass is_dg_event --- tests/test_2025_11_24.py | 5 +-- .../allowed_recipients_registry.py | 10 ++---- utils/test/event_validators/csm.py | 6 +--- utils/test/event_validators/hash_consensus.py | 14 +++----- .../node_operators_registry.py | 14 +++----- utils/test/event_validators/permission.py | 14 +++----- utils/test/event_validators/staking_router.py | 33 +++++++------------ 7 files changed, 28 insertions(+), 68 deletions(-) diff --git a/tests/test_2025_11_24.py b/tests/test_2025_11_24.py index 2e20dd8fd..24df7e1f2 100644 --- a/tests/test_2025_11_24.py +++ b/tests/test_2025_11_24.py @@ -298,15 +298,13 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g module_fee=SDVT_MODULE_MODULE_FEE_BP, treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), - emitted_by=STAKING_ROUTER, - is_dg_event=True + emitted_by=STAKING_ROUTER ) validate_set_spent_amount_event( dg_events[1], new_spent_amount=0, emitted_by=ET_TRP_REGISTRY, - is_dg_event=True, ) validate_set_limit_parameter_event( @@ -315,7 +313,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g period_duration_month=TRP_PERIOD_DURATION_MONTHS, period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, emitted_by=ET_TRP_REGISTRY, - is_dg_event=True, ) # ========================================================================= diff --git a/utils/test/event_validators/allowed_recipients_registry.py b/utils/test/event_validators/allowed_recipients_registry.py index 89bcbfcb1..bd740281e 100644 --- a/utils/test/event_validators/allowed_recipients_registry.py +++ b/utils/test/event_validators/allowed_recipients_registry.py @@ -4,7 +4,7 @@ def validate_set_limit_parameter_event( - event: EventDict, limit: int, period_duration_month: int, period_start_timestamp: int, emitted_by: str | None = None, is_dg_event: bool = False + event: EventDict, limit: int, period_duration_month: int, period_start_timestamp: int, emitted_by: str | None = None ): _events_chain = [ "LogScriptCall", @@ -12,9 +12,8 @@ def validate_set_limit_parameter_event( "CurrentPeriodAdvanced", "LimitsParametersChanged", "ScriptResult", + "Executed", ] - if is_dg_event: - _events_chain += ["Executed"] validate_events_chain([e.name for e in event], _events_chain) @@ -63,11 +62,8 @@ def validate_set_spent_amount_event( event: EventDict, new_spent_amount: int, emitted_by: str | None = None, - is_dg_event: bool = False ): - _events_chain = ["LogScriptCall", "LogScriptCall", "SpentAmountChanged", "ScriptResult"] - if is_dg_event: - _events_chain += ["Executed"] + _events_chain = ["LogScriptCall", "LogScriptCall", "SpentAmountChanged", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) diff --git a/utils/test/event_validators/csm.py b/utils/test/event_validators/csm.py index 29805ccb1..975fecec9 100644 --- a/utils/test/event_validators/csm.py +++ b/utils/test/event_validators/csm.py @@ -13,12 +13,8 @@ def validate_set_key_removal_charge_event( event: EventDict, key_removal_charge: int, emitted_by: str | None = None, - is_dg_event: bool = False, ): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "KeyRemovalChargeSet", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "KeyRemovalChargeSet", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "KeyRemovalChargeSet", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) assert event.count("KeyRemovalChargeSet") == 1 diff --git a/utils/test/event_validators/hash_consensus.py b/utils/test/event_validators/hash_consensus.py index 084ef009d..62dcfb157 100644 --- a/utils/test/event_validators/hash_consensus.py +++ b/utils/test/event_validators/hash_consensus.py @@ -3,12 +3,9 @@ from brownie import convert -def validate_hash_consensus_member_removed(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None, is_dg_event: bool = False): +def validate_hash_consensus_member_removed(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "MemberRemoved", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "MemberRemoved", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "MemberRemoved", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) @@ -23,11 +20,8 @@ def validate_hash_consensus_member_removed(event: EventDict, member: str, new_qu emitted_by ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" -def validate_hash_consensus_member_added(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None, is_dg_event: bool = False): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "MemberAdded", "ScriptResult", "Executed"] - else: - _events_chain =["LogScriptCall", "LogScriptCall", "MemberAdded", "ScriptResult"] +def validate_hash_consensus_member_added(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None): + _events_chain = ["LogScriptCall", "LogScriptCall", "MemberAdded", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) diff --git a/utils/test/event_validators/node_operators_registry.py b/utils/test/event_validators/node_operators_registry.py index 2ca183edb..079583079 100644 --- a/utils/test/event_validators/node_operators_registry.py +++ b/utils/test/event_validators/node_operators_registry.py @@ -60,12 +60,9 @@ def validate_node_operator_staking_limit_set_event( def validate_node_operator_name_set_event( - event: EventDict, node_operator_name_item: NodeOperatorNameSetItem, emitted_by: str = None, is_dg_event=False + event: EventDict, node_operator_name_item: NodeOperatorNameSetItem, emitted_by: str = None ): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorNameSet", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorNameSet", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorNameSet", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) @@ -79,12 +76,9 @@ def validate_node_operator_name_set_event( ), "Wrong event emitter" def validate_node_operator_reward_address_set_event( - event: EventDict, node_operator_reward_address_item: NodeOperatorRewardAddressSetItem, emitted_by: str = None, is_dg_event=False + event: EventDict, node_operator_reward_address_item: NodeOperatorRewardAddressSetItem, emitted_by: str = None ): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorRewardAddressSet", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorRewardAddressSet", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorRewardAddressSet", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) diff --git a/utils/test/event_validators/permission.py b/utils/test/event_validators/permission.py index 221e2de59..294b1302f 100644 --- a/utils/test/event_validators/permission.py +++ b/utils/test/event_validators/permission.py @@ -115,13 +115,10 @@ def validate_permission_grantp_event(event: EventDict, p: Permission, params: Li ), "Wrong event emitter" -def validate_grant_role_event(events: EventDict, role: str, grant_to: str, sender: str, emitted_by: str = None, is_dg_event: bool = False) -> None: +def validate_grant_role_event(events: EventDict, role: str, grant_to: str, sender: str, emitted_by: str = None) -> None: # this event chain is actual if grant role is forwarded through - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "RoleGranted", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "RoleGranted", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "RoleGranted", "ScriptResult", "Executed"] validate_events_chain([e.name for e in events], _events_chain) @@ -136,13 +133,10 @@ def validate_grant_role_event(events: EventDict, role: str, grant_to: str, sende ), "Wrong event emitter" def validate_revoke_role_event( - events: EventDict, role: str, revoke_from: str, sender: str, emitted_by: str = None, is_dg_event: bool = False + events: EventDict, role: str, revoke_from: str, sender: str, emitted_by: str = None ) -> None: - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "RoleRevoked", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "RoleRevoked", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "RoleRevoked", "ScriptResult", "Executed"] validate_events_chain([e.name for e in events], _events_chain) diff --git a/utils/test/event_validators/staking_router.py b/utils/test/event_validators/staking_router.py index 6d31471c4..dbbac148c 100644 --- a/utils/test/event_validators/staking_router.py +++ b/utils/test/event_validators/staking_router.py @@ -49,28 +49,17 @@ def validate_staking_module_added_event(event: EventDict, module_item: StakingMo assert event["StakingModuleFeesSet"]["treasuryFee"] == module_item.treasury_fee -def validate_staking_module_update_event(event: EventDict, module_item: StakingModuleItem, emitted_by: str = None, is_dg_event: bool = False): - if is_dg_event: - _events_chain = [ - "LogScriptCall", - "LogScriptCall", - "StakingModuleShareLimitSet", - "StakingModuleFeesSet", - "StakingModuleMaxDepositsPerBlockSet", - "StakingModuleMinDepositBlockDistanceSet", - "ScriptResult", - "Executed" - ] - else: - _events_chain = [ - "LogScriptCall", - "LogScriptCall", - "StakingModuleShareLimitSet", - "StakingModuleFeesSet", - "StakingModuleMaxDepositsPerBlockSet", - "StakingModuleMinDepositBlockDistanceSet", - "ScriptResult", - ] +def validate_staking_module_update_event(event: EventDict, module_item: StakingModuleItem, emitted_by: str = None): + _events_chain = [ + "LogScriptCall", + "LogScriptCall", + "StakingModuleShareLimitSet", + "StakingModuleFeesSet", + "StakingModuleMaxDepositsPerBlockSet", + "StakingModuleMinDepositBlockDistanceSet", + "ScriptResult", + "Executed" + ] validate_events_chain([e.name for e in event], _events_chain) From 8f0c8ef8e6565d877588e40c98351cee6d39ea86 Mon Sep 17 00:00:00 2001 From: dry914 Date: Fri, 14 Nov 2025 18:38:01 +0300 Subject: [PATCH 036/178] test: fix accounting tests --- configs/config_mainnet.py | 7 +- interfaces/Accounting.json | 327 ++++++++++++++++++ .../test_accounting_oracle_negative.py | 4 +- utils/config.py | 4 + utils/test/oracle_report_helpers.py | 44 +-- 5 files changed, 363 insertions(+), 23 deletions(-) create mode 100644 interfaces/Accounting.json diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 2a0b5d383..bb7782a89 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -523,5 +523,10 @@ } } -# Lido V3 - stVaults +# Lido V3 - stVaults ---------------------------------------------------------- +# ----------------------------------------------------------------------------- + VAULT_HUB = "0xe3c5C1fb61C6c81cb305D4580c53Cfa4BcB7899b" # TODO fix after mainnet deployment + +ACCOUNTING = "0x939dE020A9242b6f632BB6A47F9CE8Db897F8C38" # TODO fix after mainnet deployment +ACCOUNTING_IMPL = "0x242f9504864776Be37752050EdA0F4ac33a565C4" # TODO fix after mainnet deployment diff --git a/interfaces/Accounting.json b/interfaces/Accounting.json new file mode 100644 index 000000000..95eb78cc6 --- /dev/null +++ b/interfaces/Accounting.json @@ -0,0 +1,327 @@ +[ + { + "inputs": [ + { + "internalType": "contract ILidoLocator", + "name": "_lidoLocator", + "type": "address" + }, + { + "internalType": "contract ILido", + "name": "_lido", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "reportTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "upperBoundTimestamp", + "type": "uint256" + } + ], + "name": "IncorrectReportTimestamp", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "reportValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxValidators", + "type": "uint256" + } + ], + "name": "IncorrectReportValidators", + "type": "error" + }, + { + "inputs": [], + "name": "InternalSharesCantBeZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "operation", + "type": "string" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "NotAuthorized", + "type": "error" + }, + { + "inputs": [], + "name": "LIDO", + "outputs": [ + { + "internalType": "contract ILido", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timeElapsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "clValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "clBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawalVaultBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "elRewardsVaultBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesRequestedToBurn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "withdrawalFinalizationBatches", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "simulatedShareRate", + "type": "uint256" + } + ], + "internalType": "struct ReportValues", + "name": "_report", + "type": "tuple" + } + ], + "name": "handleOracleReport", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timeElapsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "clValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "clBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawalVaultBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "elRewardsVaultBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesRequestedToBurn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "withdrawalFinalizationBatches", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "simulatedShareRate", + "type": "uint256" + } + ], + "internalType": "struct ReportValues", + "name": "_report", + "type": "tuple" + } + ], + "name": "simulateOracleReport", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "withdrawalsVaultTransfer", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "elRewardsVaultTransfer", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "etherToFinalizeWQ", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesToFinalizeWQ", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesToBurnForWithdrawals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalSharesToBurn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesToMintAsFees", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "moduleFeeRecipients", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "moduleIds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "moduleSharesToMint", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "treasurySharesToMint", + "type": "uint256" + } + ], + "internalType": "struct Accounting.FeeDistribution", + "name": "feeDistribution", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "principalClBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preTotalShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preTotalPooledEther", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postInternalShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postInternalEther", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postTotalShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postTotalPooledEther", + "type": "uint256" + } + ], + "internalType": "struct Accounting.CalculatedValues", + "name": "update", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } + ] diff --git a/tests/acceptance/test_accounting_oracle_negative.py b/tests/acceptance/test_accounting_oracle_negative.py index 7e175e672..7c80a19d1 100644 --- a/tests/acceptance/test_accounting_oracle_negative.py +++ b/tests/acceptance/test_accounting_oracle_negative.py @@ -184,12 +184,12 @@ def test_setConsensusContract(accounting_oracle: Contract, aragon_agent: Account def test_finalize_upgrade(accounting_oracle: Contract, stranger: Account): with reverts(encode_error("InvalidContractVersionIncrement()")): - accounting_oracle.finalizeUpgrade_v2( + accounting_oracle.finalizeUpgrade_v4( 1, {"from": stranger}, ) with reverts(encode_error("InvalidContractVersionIncrement()")): - accounting_oracle.finalizeUpgrade_v2( + accounting_oracle.finalizeUpgrade_v4( 2, {"from": stranger}, ) diff --git a/utils/config.py b/utils/config.py index 7898a14e9..8a6d4bbe7 100644 --- a/utils/config.py +++ b/utils/config.py @@ -310,6 +310,10 @@ def withdrawal_queue(self) -> interface.WithdrawalQueueERC721: def vault_hub(self) -> interface.VaultHub: return interface.VaultHub(VAULT_HUB) + @property + def accounting(self) -> interface.Accounting: + return interface.Accounting(ACCOUNTING) + @property def lido_locator(self) -> interface.LidoLocator: return interface.LidoLocator(LIDO_LOCATOR) diff --git a/utils/test/oracle_report_helpers.py b/utils/test/oracle_report_helpers.py index ab3fa85d7..e584f147f 100644 --- a/utils/test/oracle_report_helpers.py +++ b/utils/test/oracle_report_helpers.py @@ -20,6 +20,9 @@ EXTRA_DATA_FORMAT_EMPTY = 0 EXTRA_DATA_FORMAT_LIST = 1 +MOCK_VAULTS_DATA_TREE_ROOT = HexBytes(ZERO_HASH) +MOCK_VAULTS_DATA_TREE_CID = "test_vaults_data_tree_cid" + @dataclass class AccountingReport: @@ -37,6 +40,8 @@ class AccountingReport: withdrawalFinalizationBatches: list[int] simulatedShareRate: int isBunkerMode: bool + vaultsDataTreeRoot: HexBytes + vaultsDataTreeCid: str extraDataFormat: int extraDataHash: HexBytes extraDataItemsCount: int @@ -90,6 +95,8 @@ def prepare_accounting_report( [int(i) for i in withdrawalFinalizationBatches], int(simulatedShareRate), bool(isBunkerMode), + MOCK_VAULTS_DATA_TREE_ROOT, + MOCK_VAULTS_DATA_TREE_CID, int(extraDataFormat), extraDataHashList[0], int(extraDataItemsCount), @@ -296,20 +303,23 @@ def simulate_report( } try: - return contracts.lido.handleOracleReport.call( - reportTime, - ONE_DAY, - beaconValidators, - postCLBalance, - withdrawalVaultBalance, - elRewardsVaultBalance, - 0, - [], - 0, + calculatedValues = contracts.accounting.simulateOracleReport.call( + [ + reportTime, + ONE_DAY, + beaconValidators, + postCLBalance, + withdrawalVaultBalance, + elRewardsVaultBalance, + 0, + [], + 0, + ], {"from": contracts.accounting_oracle.address}, block_identifier=block_identifier, override=state_override, ) + return (calculatedValues[14], calculatedValues[13], calculatedValues[0], calculatedValues[1]) except VirtualMachineError: # workaround for empty revert message from ganache on eth_call @@ -321,16 +331,8 @@ def simulate_report( [contracts.accounting_oracle.address, override_slot, refSlot], ) - contracts.lido.handleOracleReport( - reportTime, - ONE_DAY, - beaconValidators, - postCLBalance, - withdrawalVaultBalance, - elRewardsVaultBalance, - 0, - [], - 0, + contracts.accounting.handleOracleReport( + [reportTime, ONE_DAY, beaconValidators, postCLBalance, withdrawalVaultBalance, elRewardsVaultBalance, 0, [], 0], {"from": contracts.accounting_oracle.address}, ) raise # unreachable, for static analysis only @@ -519,6 +521,8 @@ def oracle_report( withdrawalFinalizationBatches=withdrawalFinalizationBatches, simulatedShareRate=simulatedShareRate, isBunkerMode=is_bunker, + vaultsDataTreeRoot=MOCK_VAULTS_DATA_TREE_ROOT, + vaultsDataTreeCid=MOCK_VAULTS_DATA_TREE_CID, extraDataFormat=extraDataFormat, extraDataHash=extraDataHashList[0], extraDataItemsCount=extraDataItemsCount, From 4da2cb6041da6558cd50b394a0b30058fd903b75 Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 17 Nov 2025 22:11:36 +0300 Subject: [PATCH 037/178] tests: v3 mainnet test deployment --- configs/config_mainnet.py | 16 +- interfaces/Lido.json | 3850 +++++++++++----------- scripts/upgrade_2025_11_24_mainnet_v3.py | 2 +- tests/test_2025_11_24_mainnet_v3.py | 246 +- 4 files changed, 2057 insertions(+), 2057 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index bb7782a89..0cda4b1b0 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -108,7 +108,7 @@ # LidoLocator LIDO_LOCATOR = "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" -LIDO_LOCATOR_IMPL = "0x9fAb5b58c1Bc180533b716B5c1D078e23cD06301" # TODO fix after mainnet deployment +LIDO_LOCATOR_IMPL = "0xE2833211Ceac805d35C4fD22c733cf3BF7F0Df54" # TODO fix after mainnet deployment # Other upgrade addresses LIDO_V2_UPGRADE_TEMPLATE = "0xa818fF9EC93122Bf9401ab4340C42De638CD600a" @@ -121,7 +121,7 @@ WITHDRAWAL_CREDENTIALS = "0x010000000000000000000000b9d7934878b5fb9610b3fe8a5e441e8fad7e293f" # Lido -LIDO_IMPL = "0xE1CF94253EF9F9d01FDd5fDdd54fDE1D1Fd7F5A6" # TODO fix after mainnet deployment +LIDO_IMPL = "0xaA5D5c3404678dfF147004Bf6849155f1448D261" # TODO fix after mainnet deployment # see Lido's proxy appId() LIDO_ARAGON_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" LIDO_MAX_STAKE_LIMIT_ETH = 150_000 @@ -201,7 +201,7 @@ EXIT_EVENTS_LOOKBACK_WINDOW_IN_SLOTS = 14 * 7200 # 14 days # OracleReportSanityChecker -ORACLE_REPORT_SANITY_CHECKER = "0x1C1817e6f03F543ff4Bd86C25a45e921800DEf7a" # TODO fix after mainnet deployment +ORACLE_REPORT_SANITY_CHECKER = "0x5d073dB5EcB9E006258bE05566e94ED0012daD44" # TODO fix after mainnet deployment APPEARED_VALIDATORS_PER_DAY_LIMIT = 1800 EXITED_VALIDATORS_PER_DAY_LIMIT = 3600 ANNUAL_BALANCE_INCREASE_BP_LIMIT = 1000 # 10% @@ -216,7 +216,7 @@ CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 50 # Burner -BURNER = "0xb70b8cC263361FBf8b60E20569be809809Ff1537" # TODO fix after mainnet deployment +BURNER = "0x29783959aC9E3Eb0659C208f69aA174e26b5e3dD" # TODO fix after mainnet deployment TOTAL_NON_COVER_SHARES_BURNT = 32145684728326685744 TOTAL_COVER_SHARES_BURNT = 0 @@ -239,7 +239,7 @@ # AccountingOracle # and its corresponding HashConsensus ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" -ACCOUNTING_ORACLE_IMPL = "0x6cC54EF252B498B0Efb0b6d450a09EE5833104b2" # TODO fix after mainnet deployment +ACCOUNTING_ORACLE_IMPL = "0xb840D4D4cA5C9ce5202AEd502A18BE9831042F21" # TODO fix after mainnet deployment HASH_CONSENSUS_FOR_AO = "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288" AO_EPOCHS_PER_FRAME = 225 AO_FAST_LANE_LENGTH_SLOTS = 100 @@ -526,7 +526,7 @@ # Lido V3 - stVaults ---------------------------------------------------------- # ----------------------------------------------------------------------------- -VAULT_HUB = "0xe3c5C1fb61C6c81cb305D4580c53Cfa4BcB7899b" # TODO fix after mainnet deployment +VAULT_HUB = "0x8Ba5200ffc0CE49993FB6446f636BF9Fc5a36FD7" # TODO fix after mainnet deployment -ACCOUNTING = "0x939dE020A9242b6f632BB6A47F9CE8Db897F8C38" # TODO fix after mainnet deployment -ACCOUNTING_IMPL = "0x242f9504864776Be37752050EdA0F4ac33a565C4" # TODO fix after mainnet deployment +ACCOUNTING = "0x5f22363Da639c23cEee8b223aEc440A6710C3242" # TODO fix after mainnet deployment +ACCOUNTING_IMPL = "0xB58852416D3CA4722207F63dB7430261eDF8E16a" # TODO fix after mainnet deployment diff --git a/interfaces/Lido.json b/interfaces/Lido.json index 0389d73d4..e62344e9b 100644 --- a/interfaces/Lido.json +++ b/interfaces/Lido.json @@ -1,1926 +1,1926 @@ [ - { - "constant": true, - "inputs": [ - { - "name": "_sharesAmount", - "type": "uint256" - } - ], - "name": "getPooledEthBySharesRoundUp", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "resume", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_recipient", - "type": "address" - }, - { - "name": "_amountOfShares", - "type": "uint256" - } - ], - "name": "mintExternalShares", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "stop", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "hasInitialized", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "STAKING_CONTROL_ROLE", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_ethAmount", - "type": "uint256" - } - ], - "name": "getSharesByPooledEth", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_reportTimestamp", - "type": "uint256" - }, - { - "name": "_timeElapsed", - "type": "uint256" - }, - { - "name": "_preTotalShares", - "type": "uint256" - }, - { - "name": "_preTotalEther", - "type": "uint256" - }, - { - "name": "_postTotalShares", - "type": "uint256" - }, - { - "name": "_postTotalEther", - "type": "uint256" - }, - { - "name": "_postInternalShares", - "type": "uint256" - }, - { - "name": "_postInternalEther", - "type": "uint256" - }, - { - "name": "_sharesMintedAsFees", - "type": "uint256" - } - ], - "name": "emitTokenRebase", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isStakingPaused", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_sender", - "type": "address" - }, - { - "name": "_recipient", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_script", - "type": "bytes" - } - ], - "name": "getEVMScriptExecutor", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_maxStakeLimit", - "type": "uint256" - }, - { - "name": "_stakeLimitIncreasePerBlock", - "type": "uint256" - } - ], - "name": "setStakingLimit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "RESUME_ROLE", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getRecoveryVault", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "DOMAIN_SEPARATOR", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTotalPooledEther", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_newDepositedValidators", - "type": "uint256" - } - ], - "name": "unsafeChangeDepositedValidators", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "PAUSE_ROLE", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTreasury", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isStopped", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getBufferedEther", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_lidoLocator", - "type": "address" - }, - { - "name": "_eip712StETH", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "receiveELRewards", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_recipient", - "type": "address" - }, - { - "name": "_amountOfShares", - "type": "uint256" - } - ], - "name": "mintShares", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getWithdrawalCredentials", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_reportTimestamp", - "type": "uint256" - }, - { - "name": "_preClValidators", - "type": "uint256" - }, - { - "name": "_reportClValidators", - "type": "uint256" - }, - { - "name": "_reportClBalance", - "type": "uint256" - } - ], - "name": "processClStateUpdate", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getCurrentStakeLimit", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getExternalShares", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amountOfShares", - "type": "uint256" - } - ], - "name": "internalizeExternalBadDebt", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getStakeLimitFullInfo", - "outputs": [ - { - "name": "isStakingPaused_", - "type": "bool" - }, - { - "name": "isStakingLimitSet", - "type": "bool" - }, - { - "name": "currentStakeLimit", - "type": "uint256" - }, - { - "name": "maxStakeLimit", - "type": "uint256" - }, - { - "name": "maxStakeLimitGrowthBlocks", - "type": "uint256" - }, - { - "name": "prevStakeLimit", - "type": "uint256" - }, - { - "name": "prevStakeBlockNumber", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_sender", - "type": "address" - }, - { - "name": "_recipient", - "type": "address" - }, - { - "name": "_sharesAmount", - "type": "uint256" - } - ], - "name": "transferSharesFrom", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amountOfShares", - "type": "uint256" - } - ], - "name": "burnExternalShares", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "resumeStaking", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getFeeDistribution", - "outputs": [ - { - "name": "treasuryFeeBasisPoints", - "type": "uint16" - }, - { - "name": "insuranceFeeBasisPoints", - "type": "uint16" - }, - { - "name": "operatorsFeeBasisPoints", - "type": "uint16" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "receiveWithdrawals", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_sharesAmount", - "type": "uint256" - } - ], - "name": "getPooledEthByShares", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amountOfShares", - "type": "uint256" - } - ], - "name": "rebalanceExternalEtherToInternal", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "token", - "type": "address" - } - ], - "name": "allowRecoverability", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "owner", - "type": "address" - } - ], - "name": "nonces", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "appId", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "eip712Domain", - "outputs": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_amountOfShares", - "type": "uint256" - } - ], - "name": "burnShares", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_oldBurner", - "type": "address" - }, - { - "name": "_contractsWithBurnerAllowances", - "type": "address[]" - }, - { - "name": "_initialMaxExternalRatioBP", - "type": "uint256" - } - ], - "name": "finalizeUpgrade_v3", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getMaxMintableExternalShares", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getContractVersion", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getInitializationBlock", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_recipient", - "type": "address" - }, - { - "name": "_sharesAmount", - "type": "uint256" - } - ], - "name": "transferShares", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_reportTimestamp", - "type": "uint256" - }, - { - "name": "_reportClBalance", - "type": "uint256" - }, - { - "name": "_principalCLBalance", - "type": "uint256" - }, - { - "name": "_withdrawalsToWithdraw", - "type": "uint256" - }, - { - "name": "_elRewardsToWithdraw", - "type": "uint256" - }, - { - "name": "_lastWithdrawalRequestToFinalize", - "type": "uint256" - }, - { - "name": "_withdrawalsShareRate", - "type": "uint256" - }, - { - "name": "_etherToLockOnWithdrawalQueue", - "type": "uint256" - } - ], - "name": "collectRewardsAndProcessWithdrawals", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getEIP712StETH", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getMaxExternalRatioBP", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "transferToVault", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_sender", - "type": "address" - }, - { - "name": "_role", - "type": "bytes32" - }, - { - "name": "_params", - "type": "uint256[]" - } - ], - "name": "canPerform", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_referral", - "type": "address" - } - ], - "name": "submit", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getEVMScriptRegistry", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_recipient", - "type": "address" - }, - { - "name": "_amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_maxDepositsCount", - "type": "uint256" - }, - { - "name": "_stakingModuleId", - "type": "uint256" - }, - { - "name": "_depositCalldata", - "type": "bytes" - } - ], - "name": "deposit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getBeaconStat", - "outputs": [ - { - "name": "depositedValidators", - "type": "uint256" - }, - { - "name": "beaconValidators", - "type": "uint256" - }, - { - "name": "beaconBalance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "removeStakingLimit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getFee", - "outputs": [ - { - "name": "totalFee", - "type": "uint16" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "kernel", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTotalShares", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - }, - { - "name": "_deadline", - "type": "uint256" - }, - { - "name": "_v", - "type": "uint8" - }, - { - "name": "_r", - "type": "bytes32" - }, - { - "name": "_s", - "type": "bytes32" - } - ], - "name": "permit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "isPetrified", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getExternalEther", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getLidoLocator", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "canDeposit", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "STAKING_PAUSE_ROLE", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getDepositableEther", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_maxExternalRatioBP", - "type": "uint256" - } - ], - "name": "setMaxExternalRatioBP", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_account", - "type": "address" - } - ], - "name": "sharesOf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "pauseStaking", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTotalELRewardsCollected", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "payable": true, - "stateMutability": "payable", - "type": "fallback" - }, - { - "anonymous": false, - "inputs": [], - "name": "StakingPaused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "StakingResumed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "maxStakeLimit", - "type": "uint256" - }, - { - "indexed": false, - "name": "stakeLimitIncreasePerBlock", - "type": "uint256" - } - ], - "name": "StakingLimitSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "StakingLimitRemoved", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "reportTimestamp", - "type": "uint256" - }, - { - "indexed": false, - "name": "preCLValidators", - "type": "uint256" - }, - { - "indexed": false, - "name": "postCLValidators", - "type": "uint256" - } - ], - "name": "CLValidatorsUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "depositedValidators", - "type": "uint256" - } - ], - "name": "DepositedValidatorsChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "reportTimestamp", - "type": "uint256" - }, - { - "indexed": false, - "name": "preCLBalance", - "type": "uint256" - }, - { - "indexed": false, - "name": "postCLBalance", - "type": "uint256" - }, - { - "indexed": false, - "name": "withdrawalsWithdrawn", - "type": "uint256" - }, - { - "indexed": false, - "name": "executionLayerRewardsWithdrawn", - "type": "uint256" - }, - { - "indexed": false, - "name": "postBufferedEther", - "type": "uint256" - } - ], - "name": "ETHDistributed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "reportTimestamp", - "type": "uint256" - }, - { - "indexed": false, - "name": "timeElapsed", - "type": "uint256" - }, - { - "indexed": false, - "name": "preTotalShares", - "type": "uint256" - }, - { - "indexed": false, - "name": "preTotalEther", - "type": "uint256" - }, - { - "indexed": false, - "name": "postTotalShares", - "type": "uint256" - }, - { - "indexed": false, - "name": "postTotalEther", - "type": "uint256" - }, - { - "indexed": false, - "name": "sharesMintedAsFees", - "type": "uint256" - } - ], - "name": "TokenRebased", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "lidoLocator", - "type": "address" - } - ], - "name": "LidoLocatorSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "ELRewardsReceived", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "WithdrawalsReceived", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - }, - { - "indexed": false, - "name": "referral", - "type": "address" - } - ], - "name": "Submitted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "Unbuffered", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "reportTimestamp", - "type": "uint256" - }, - { - "indexed": false, - "name": "postInternalShares", - "type": "uint256" - }, - { - "indexed": false, - "name": "postInternalEther", - "type": "uint256" - }, - { - "indexed": false, - "name": "sharesMintedAsFees", - "type": "uint256" - } - ], - "name": "InternalShareRateUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "receiver", - "type": "address" - }, - { - "indexed": false, - "name": "amountOfShares", - "type": "uint256" - } - ], - "name": "ExternalSharesMinted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "amountOfShares", - "type": "uint256" - } - ], - "name": "ExternalSharesBurnt", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "maxExternalRatioBP", - "type": "uint256" - } - ], - "name": "MaxExternalRatioBPSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "ExternalEtherTransferredToBuffer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "amountOfShares", - "type": "uint256" - } - ], - "name": "ExternalBadDebtInternalized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "executor", - "type": "address" - }, - { - "indexed": false, - "name": "script", - "type": "bytes" - }, - { - "indexed": false, - "name": "input", - "type": "bytes" - }, - { - "indexed": false, - "name": "returnData", - "type": "bytes" - } - ], - "name": "ScriptResult", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "vault", - "type": "address" - }, - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "amount", - "type": "uint256" - } - ], - "name": "RecoverToVault", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "eip712StETH", - "type": "address" - } - ], - "name": "EIP712StETHInitialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "sharesValue", - "type": "uint256" - } - ], - "name": "TransferShares", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "account", - "type": "address" - }, - { - "indexed": false, - "name": "preRebaseTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "postRebaseTokenAmount", - "type": "uint256" - }, - { - "indexed": false, - "name": "sharesAmount", - "type": "uint256" - } - ], - "name": "SharesBurnt", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "Stopped", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "Resumed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "name": "version", - "type": "uint256" - } - ], - "name": "ContractVersionSet", - "type": "event" - } - ] + { + "constant": true, + "inputs": [ + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "getPooledEthBySharesRoundUp", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resume", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "mintExternalShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "stop", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "hasInitialized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKING_CONTROL_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_ethAmount", + "type": "uint256" + } + ], + "name": "getSharesByPooledEth", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_timeElapsed", + "type": "uint256" + }, + { + "name": "_preTotalShares", + "type": "uint256" + }, + { + "name": "_preTotalEther", + "type": "uint256" + }, + { + "name": "_postTotalShares", + "type": "uint256" + }, + { + "name": "_postTotalEther", + "type": "uint256" + }, + { + "name": "_postInternalShares", + "type": "uint256" + }, + { + "name": "_postInternalEther", + "type": "uint256" + }, + { + "name": "_sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "emitTokenRebase", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isStakingPaused", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_script", + "type": "bytes" + } + ], + "name": "getEVMScriptExecutor", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxStakeLimit", + "type": "uint256" + }, + { + "name": "_stakeLimitIncreasePerBlock", + "type": "uint256" + } + ], + "name": "setStakingLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getRecoveryVault", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalPooledEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newDepositedValidators", + "type": "uint256" + } + ], + "name": "unsafeChangeDepositedValidators", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTreasury", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isStopped", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBufferedEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_lidoLocator", + "type": "address" + }, + { + "name": "_eip712StETH", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "receiveELRewards", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "mintShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getWithdrawalCredentials", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_preClValidators", + "type": "uint256" + }, + { + "name": "_reportClValidators", + "type": "uint256" + }, + { + "name": "_reportClBalance", + "type": "uint256" + } + ], + "name": "processClStateUpdate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentStakeLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getExternalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "internalizeExternalBadDebt", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getStakeLimitFullInfo", + "outputs": [ + { + "name": "isStakingPaused_", + "type": "bool" + }, + { + "name": "isStakingLimitSet", + "type": "bool" + }, + { + "name": "currentStakeLimit", + "type": "uint256" + }, + { + "name": "maxStakeLimit", + "type": "uint256" + }, + { + "name": "maxStakeLimitGrowthBlocks", + "type": "uint256" + }, + { + "name": "prevStakeLimit", + "type": "uint256" + }, + { + "name": "prevStakeBlockNumber", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "transferSharesFrom", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "burnExternalShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resumeStaking", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFeeDistribution", + "outputs": [ + { + "name": "treasuryFeeBasisPoints", + "type": "uint16" + }, + { + "name": "insuranceFeeBasisPoints", + "type": "uint16" + }, + { + "name": "operatorsFeeBasisPoints", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "receiveWithdrawals", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "getPooledEthByShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "rebalanceExternalEtherToInternal", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "token", + "type": "address" + } + ], + "name": "allowRecoverability", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "appId", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "burnShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_oldBurner", + "type": "address" + }, + { + "name": "_contractsWithBurnerAllowances", + "type": "address[]" + }, + { + "name": "_initialMaxExternalRatioBP", + "type": "uint256" + } + ], + "name": "finalizeUpgrade_v3", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getMaxMintableExternalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getContractVersion", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getInitializationBlock", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "transferShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_reportClBalance", + "type": "uint256" + }, + { + "name": "_principalCLBalance", + "type": "uint256" + }, + { + "name": "_withdrawalsToWithdraw", + "type": "uint256" + }, + { + "name": "_elRewardsToWithdraw", + "type": "uint256" + }, + { + "name": "_lastWithdrawalRequestToFinalize", + "type": "uint256" + }, + { + "name": "_withdrawalsShareRate", + "type": "uint256" + }, + { + "name": "_etherToLockOnWithdrawalQueue", + "type": "uint256" + } + ], + "name": "collectRewardsAndProcessWithdrawals", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEIP712StETH", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getMaxExternalRatioBP", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "transferToVault", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + }, + { + "name": "_params", + "type": "uint256[]" + } + ], + "name": "canPerform", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_referral", + "type": "address" + } + ], + "name": "submit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEVMScriptRegistry", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxDepositsCount", + "type": "uint256" + }, + { + "name": "_stakingModuleId", + "type": "uint256" + }, + { + "name": "_depositCalldata", + "type": "bytes" + } + ], + "name": "deposit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBeaconStat", + "outputs": [ + { + "name": "depositedValidators", + "type": "uint256" + }, + { + "name": "beaconValidators", + "type": "uint256" + }, + { + "name": "beaconBalance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "removeStakingLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFee", + "outputs": [ + { + "name": "totalFee", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kernel", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_deadline", + "type": "uint256" + }, + { + "name": "_v", + "type": "uint8" + }, + { + "name": "_r", + "type": "bytes32" + }, + { + "name": "_s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isPetrified", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getExternalEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getLidoLocator", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "canDeposit", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKING_PAUSE_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getDepositableEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxExternalRatioBP", + "type": "uint256" + } + ], + "name": "setMaxExternalRatioBP", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_account", + "type": "address" + } + ], + "name": "sharesOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "pauseStaking", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalELRewardsCollected", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingResumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "maxStakeLimit", + "type": "uint256" + }, + { + "indexed": false, + "name": "stakeLimitIncreasePerBlock", + "type": "uint256" + } + ], + "name": "StakingLimitSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingLimitRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "preCLValidators", + "type": "uint256" + }, + { + "indexed": false, + "name": "postCLValidators", + "type": "uint256" + } + ], + "name": "CLValidatorsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "depositedValidators", + "type": "uint256" + } + ], + "name": "DepositedValidatorsChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "preCLBalance", + "type": "uint256" + }, + { + "indexed": false, + "name": "postCLBalance", + "type": "uint256" + }, + { + "indexed": false, + "name": "withdrawalsWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "name": "executionLayerRewardsWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "name": "postBufferedEther", + "type": "uint256" + } + ], + "name": "ETHDistributed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "timeElapsed", + "type": "uint256" + }, + { + "indexed": false, + "name": "preTotalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "preTotalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "postTotalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "postTotalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "TokenRebased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "lidoLocator", + "type": "address" + } + ], + "name": "LidoLocatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "ELRewardsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawalsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "name": "referral", + "type": "address" + } + ], + "name": "Submitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Unbuffered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "postInternalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "postInternalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "InternalShareRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalSharesMinted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalSharesBurnt", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "maxExternalRatioBP", + "type": "uint256" + } + ], + "name": "MaxExternalRatioBPSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "ExternalEtherTransferredToBuffer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalBadDebtInternalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "executor", + "type": "address" + }, + { + "indexed": false, + "name": "script", + "type": "bytes" + }, + { + "indexed": false, + "name": "input", + "type": "bytes" + }, + { + "indexed": false, + "name": "returnData", + "type": "bytes" + } + ], + "name": "ScriptResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "RecoverToVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "eip712StETH", + "type": "address" + } + ], + "name": "EIP712StETHInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "sharesValue", + "type": "uint256" + } + ], + "name": "TransferShares", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "account", + "type": "address" + }, + { + "indexed": false, + "name": "preRebaseTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "postRebaseTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesAmount", + "type": "uint256" + } + ], + "name": "SharesBurnt", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Stopped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "version", + "type": "uint256" + } + ], + "name": "ContractVersionSet", + "type": "event" + } +] diff --git a/scripts/upgrade_2025_11_24_mainnet_v3.py b/scripts/upgrade_2025_11_24_mainnet_v3.py index 41192cf4e..b0d4bb707 100644 --- a/scripts/upgrade_2025_11_24_mainnet_v3.py +++ b/scripts/upgrade_2025_11_24_mainnet_v3.py @@ -21,7 +21,7 @@ # ============================== Addresses =================================== -OMNIBUS_CONTRACT = "0xA3710716965497e62bC3165Eb7DD2a1B1437f8Af" # TODO replace with the actual omnibus contract address +OMNIBUS_CONTRACT = "0xda9D35b108f9538F8025f1c692265b9451D42f8b" # TODO replace with the actual omnibus contract address # ============================= Description ================================== diff --git a/tests/test_2025_11_24_mainnet_v3.py b/tests/test_2025_11_24_mainnet_v3.py index 7a5851a15..5f78d626c 100644 --- a/tests/test_2025_11_24_mainnet_v3.py +++ b/tests/test_2025_11_24_mainnet_v3.py @@ -239,129 +239,129 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ) - if EXPECTED_DG_PROPOSAL_ID is not None: - details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) - if details["status"] != PROPOSAL_STATUS["executed"]: - # ========================================================================= - # ================== DG before proposal executed checks =================== - # ========================================================================= - # TODO add DG before proposal executed checks - - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() - trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() - assert trp_limit_before == TRP_LIMIT_BEFORE - assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS - assert trp_already_spent_amount_before == TRP_ALREADY_SPENT_BEFORE - assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - TRP_ALREADY_SPENT_BEFORE - assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP - assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP - - # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) - assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP - assert sdvt_module_before['id'] == SDVT_MODULE_ID - assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP - assert sdvt_module_before['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP - assert sdvt_module_before['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP - assert sdvt_module_before['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK - assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - assert sdvt_module_before['name'] == SDVT_MODULE_NAME - - - if details["status"] == PROPOSAL_STATUS["submitted"]: - chain.sleep(timelock.getAfterSubmitDelay() + 1) - dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - - # if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: - # chain.sleep(timelock.getAfterScheduleDelay() + 1) - # dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - # display_dg_events(dg_tx) - # dg_events = group_dg_events_from_receipt( - # dg_tx, - # timelock=EMERGENCY_PROTECTED_TIMELOCK, - # admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - # ) - # assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT - # assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT - - # # TODO validate all DG events - - # validate_set_spent_amount_event( - # dg_events[0], - # new_spent_amount=0, - # emitted_by=ET_TRP_REGISTRY, - # is_dg_event=True, - # ) - - # validate_set_limit_parameter_event( - # dg_events[1], - # limit=TRP_LIMIT_AFTER, - # period_duration_month=TRP_PERIOD_DURATION_MONTHS, - # period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, - # emitted_by=ET_TRP_REGISTRY, - # is_dg_event=True, - # ) - - # validate_staking_module_update_event( - # event=dg_events[2], - # module_item=StakingModuleItem( - # id=SDVT_MODULE_ID, - # name=SDVT_MODULE_NAME, - # address=None, - # target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, - # module_fee=SDVT_MODULE_MODULE_FEE_BP, - # treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, - # priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), - # emitted_by=STAKING_ROUTER, - # is_dg_event=True - # ) - - # ========================================================================= - # ==================== After DG proposal executed checks ================== - # ========================================================================= - # TODO add DG after proposal executed checks - - # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() - trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() - # assert trp_limit_after == TRP_LIMIT_AFTER - # assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS - # assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER - # assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER - # assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP - # assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - - # # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - # sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) - # assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP - # assert sdvt_module_after['id'] == SDVT_MODULE_ID - # assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP - # assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP - # assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP - # assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK - # assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - # assert sdvt_module_after['name'] == SDVT_MODULE_NAME - # # additional checks to make sure no other fields were changed - # assert sdvt_module_after['id'] == sdvt_module_before['id'] - # assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] - # assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] - # assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] - # assert sdvt_module_after['status'] == sdvt_module_before['status'] - # assert sdvt_module_after['name'] == sdvt_module_before['name'] - # assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] - # assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] - # assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] - # assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] - # assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] - # assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] - # assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) - # assert len(sdvt_module_after.items()) == 13 - - # # additional test for TRP ET factory behavior after the vote - # trp_limit_test(stranger) + # if EXPECTED_DG_PROPOSAL_ID is not None: + # details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) + # if details["status"] != PROPOSAL_STATUS["executed"]: + # # ========================================================================= + # # ================== DG before proposal executed checks =================== + # # ========================================================================= + # # TODO add DG before proposal executed checks + + # # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + # # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + # trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() + # trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() + # assert trp_limit_before == TRP_LIMIT_BEFORE + # assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS + # assert trp_already_spent_amount_before == TRP_ALREADY_SPENT_BEFORE + # assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - TRP_ALREADY_SPENT_BEFORE + # assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP + # assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP + + # # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) + # assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP + # assert sdvt_module_before['id'] == SDVT_MODULE_ID + # assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + # assert sdvt_module_before['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + # assert sdvt_module_before['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + # assert sdvt_module_before['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + # assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + # assert sdvt_module_before['name'] == SDVT_MODULE_NAME + + + # if details["status"] == PROPOSAL_STATUS["submitted"]: + # chain.sleep(timelock.getAfterSubmitDelay() + 1) + # dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + + # # if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: + # # chain.sleep(timelock.getAfterScheduleDelay() + 1) + # # dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + # # display_dg_events(dg_tx) + # # dg_events = group_dg_events_from_receipt( + # # dg_tx, + # # timelock=EMERGENCY_PROTECTED_TIMELOCK, + # # admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + # # ) + # # assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + # # assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + + # # # TODO validate all DG events + + # # validate_set_spent_amount_event( + # # dg_events[0], + # # new_spent_amount=0, + # # emitted_by=ET_TRP_REGISTRY, + # # is_dg_event=True, + # # ) + + # # validate_set_limit_parameter_event( + # # dg_events[1], + # # limit=TRP_LIMIT_AFTER, + # # period_duration_month=TRP_PERIOD_DURATION_MONTHS, + # # period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, + # # emitted_by=ET_TRP_REGISTRY, + # # is_dg_event=True, + # # ) + + # # validate_staking_module_update_event( + # # event=dg_events[2], + # # module_item=StakingModuleItem( + # # id=SDVT_MODULE_ID, + # # name=SDVT_MODULE_NAME, + # # address=None, + # # target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, + # # module_fee=SDVT_MODULE_MODULE_FEE_BP, + # # treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, + # # priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + # # emitted_by=STAKING_ROUTER, + # # is_dg_event=True + # # ) + + # # ========================================================================= + # # ==================== After DG proposal executed checks ================== + # # ========================================================================= + # # TODO add DG after proposal executed checks + + # # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + # # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + # trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() + # trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() + # # assert trp_limit_after == TRP_LIMIT_AFTER + # # assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS + # # assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER + # # assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + # # assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP + # # assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP + + # # # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # # sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) + # # assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP + # # assert sdvt_module_after['id'] == SDVT_MODULE_ID + # # assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + # # assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + # # assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + # # assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + # # assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + # # assert sdvt_module_after['name'] == SDVT_MODULE_NAME + # # # additional checks to make sure no other fields were changed + # # assert sdvt_module_after['id'] == sdvt_module_before['id'] + # # assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] + # # assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] + # # assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] + # # assert sdvt_module_after['status'] == sdvt_module_before['status'] + # # assert sdvt_module_after['name'] == sdvt_module_before['name'] + # # assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] + # # assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] + # # assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] + # # assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] + # # assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] + # # assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] + # # assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) + # # assert len(sdvt_module_after.items()) == 13 + + # # # additional test for TRP ET factory behavior after the vote + # # trp_limit_test(stranger) def trp_limit_test(stranger): From dc8cdacd72143c89b2a564c721c45fcfd99a677c Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 18 Nov 2025 13:13:15 +0300 Subject: [PATCH 038/178] test: add 0.8.25 solidity --- Dockerfile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Dockerfile b/Dockerfile index c6aa75b08..44b4acc37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -233,6 +233,23 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then \ git clean -d -x -f; \ fi +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + # build solc-v0.8.25 + git checkout v0.8.25; \ + # the compiler throws warnings when compiling this version, and the warnings are treated as errors. + # we disable treating the warnings as errors, unless the build doesn't succeed + grep -rl '\-Werror' ./cmake/EthCompilerSettings.cmake | xargs sed -i 's/\-Werror/\-Wno\-error/g'; \ + # there is no sudo in the container, but we are under root so we do not need it + grep -rl 'sudo make install' ./scripts/build.sh | xargs sed -i 's/sudo make install/make install/g'; \ + # build solc faster + grep -rl 'make -j2' ./scripts/build.sh | xargs sed -i 's/make -j2/make -j4/g'; \ + ./scripts/build.sh; \ + mv /usr/local/bin/solc /root/.solcx/solc-v0.8.25; \ + git checkout .; \ + git checkout develop; \ + git clean -d -x -f; \ + fi + RUN if [ "$TARGETARCH" = "arm64" ]; then \ # build solc-v0.6.11 git checkout v0.6.11; \ @@ -303,6 +320,7 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.7.6 --version | gr RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.15 --version | grep 'Version: 0.8.15+commit.e14f2714' || (echo "Incorrect solc-v0.8.15 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.19 --version | grep 'Version: 0.8.19+commit.7dd6d404' || (echo "Incorrect solc-v0.8.19 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.24 --version | grep 'Version: 0.8.24+commit.e11b9ed9' || (echo "Incorrect solc-v0.8.24 version" && exit 1) fi +RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.25 --version | grep 'Version: 0.8.25+commit.b61c2a91' || (echo "Incorrect solc-v0.8.25 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.6.11 --version | grep 'Version: 0.6.11+commit.5ef660b1' || (echo "Incorrect solc-v0.6.11 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.vvm/vyper-0.3.7 --version | grep '0.3.7+' || (echo "Incorrect vyper-0.3.7 version" && exit 1) fi From 2370133038f76f9222d2a962e5f73151d221f28c Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 18 Nov 2025 16:33:22 +0300 Subject: [PATCH 039/178] Update Docker image version in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db3f5e578..5067f6fad 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ _*may be optionally set when running tests asynchronously to reduce the risk of Run the container in the `scripts` directory and specify the ENV VARs: ```shell -docker run --name scripts -v "$(pwd)":/root/scripts -e ETH_RPC_URL -e ETH_RPC_URL2 -e ETH_RPC_URL3 -e PINATA_CLOUD_TOKEN -e DEPLOYER -e ETHERSCAN_TOKEN -e ETHERSCAN_TOKEN2 -e ETHERSCAN_TOKEN3 -d ghcr.io/lidofinance/scripts:v19 +docker run --name scripts -v "$(pwd)":/root/scripts -e ETH_RPC_URL -e ETH_RPC_URL2 -e ETH_RPC_URL3 -e PINATA_CLOUD_TOKEN -e DEPLOYER -e ETHERSCAN_TOKEN -e ETHERSCAN_TOKEN2 -e ETHERSCAN_TOKEN3 -d ghcr.io/lidofinance/scripts:v20 ``` #### Step 4. Initialize container From 25dbb5b128abbf9943178da5088c1c1dbe67fb93 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 18 Nov 2025 21:54:54 +0300 Subject: [PATCH 040/178] test: fix burner tests --- .github/workflows/core_tests.yml | 2 +- interfaces/Burner.json | 432 ++++++++++++++++++++++---- tests/acceptance/test_burner.py | 4 +- tests/regression/test_pause_resume.py | 9 +- 4 files changed, 382 insertions(+), 65 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index bafb16dbc..2ab539103 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -21,7 +21,7 @@ jobs: services: tests-runner: - image: ghcr.io/lidofinance/scripts:v19 + image: ghcr.io/lidofinance/scripts:v20 ports: - 8546:8546 volumes: diff --git a/interfaces/Burner.json b/interfaces/Burner.json index ac643f234..edaffbaa9 100644 --- a/interfaces/Burner.json +++ b/interfaces/Burner.json @@ -1,24 +1,25 @@ [ { "inputs": [ - { "internalType": "address", "name": "_admin", "type": "address" }, - { "internalType": "address", "name": "_treasury", "type": "address" }, - { "internalType": "address", "name": "_stETH", "type": "address" }, { - "internalType": "uint256", - "name": "_totalCoverSharesBurnt", - "type": "uint256" + "internalType": "address", + "name": "_locator", + "type": "address" }, { - "internalType": "uint256", - "name": "_totalNonCoverSharesBurnt", - "type": "uint256" + "internalType": "address", + "name": "_stETH", + "type": "address" } ], "stateMutability": "nonpayable", "type": "constructor" }, - { "inputs": [], "name": "AppAuthLidoFailed", "type": "error" }, + { + "inputs": [], + "name": "AppAuthFailed", + "type": "error" + }, { "inputs": [ { @@ -26,20 +27,100 @@ "name": "requestedAmount", "type": "uint256" }, - { "internalType": "uint256", "name": "actualAmount", "type": "uint256" } + { + "internalType": "uint256", + "name": "actualAmount", + "type": "uint256" + } ], "name": "BurnAmountExceedsActual", "type": "error" }, - { "inputs": [], "name": "DirectETHTransfer", "type": "error" }, - { "inputs": [], "name": "StETHRecoveryWrongFunc", "type": "error" }, { - "inputs": [{ "internalType": "string", "name": "field", "type": "string" }], + "inputs": [], + "name": "DirectETHTransfer", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidContractVersionIncrement", + "type": "error" + }, + { + "inputs": [], + "name": "MigrationNotAllowedOrAlreadyMigrated", + "type": "error" + }, + { + "inputs": [], + "name": "NonZeroContractVersionOnInit", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyLidoCanMigrate", + "type": "error" + }, + { + "inputs": [], + "name": "StETHRecoveryWrongFunc", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + } + ], + "name": "UnexpectedContractVersion", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "field", + "type": "string" + } + ], "name": "ZeroAddress", "type": "error" }, - { "inputs": [], "name": "ZeroBurnAmount", "type": "error" }, - { "inputs": [], "name": "ZeroRecoveryAmount", "type": "error" }, + { + "inputs": [], + "name": "ZeroBurnAmount", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroRecoveryAmount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "version", + "type": "uint256" + } + ], + "name": "ContractVersionSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -249,93 +330,192 @@ { "inputs": [], "name": "DEFAULT_ADMIN_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "REQUEST_BURN_MY_STETH_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "LIDO", + "outputs": [ + { + "internalType": "contract ILido", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "REQUEST_BURN_SHARES_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "STETH", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "REQUEST_BURN_MY_STETH_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "TREASURY", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "REQUEST_BURN_SHARES_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "uint256", "name": "_sharesToBurn", "type": "uint256" } + { + "internalType": "uint256", + "name": "_sharesToBurn", + "type": "uint256" + } ], "name": "commitSharesToBurn", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "getContractVersion", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getCoverSharesBurnt", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getExcessStETH", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getNonCoverSharesBurnt", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } ], "name": "getRoleAdmin", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "uint256", "name": "index", "type": "uint256" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } ], "name": "getRoleMember", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } ], "name": "getRoleMemberCount", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, @@ -343,16 +523,32 @@ "inputs": [], "name": "getSharesRequestedToBurn", "outputs": [ - { "internalType": "uint256", "name": "coverShares", "type": "uint256" }, - { "internalType": "uint256", "name": "nonCoverShares", "type": "uint256" } + { + "internalType": "uint256", + "name": "coverShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonCoverShares", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "grantRole", "outputs": [], @@ -361,18 +557,84 @@ }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "hasRole", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "uint256", "name": "_amount", "type": "uint256" } + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isMigrationAllowed", + "type": "bool" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isMigrationAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oldBurner", + "type": "address" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } ], "name": "recoverERC20", "outputs": [], @@ -381,8 +643,16 @@ }, { "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "uint256", "name": "_tokenId", "type": "uint256" } + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } ], "name": "recoverERC721", "outputs": [], @@ -398,14 +668,35 @@ }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "renounceRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_sharesAmountToBurn", + "type": "uint256" + } + ], + "name": "requestBurnMyShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -434,7 +725,11 @@ }, { "inputs": [ - { "internalType": "address", "name": "_from", "type": "address" }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, { "internalType": "uint256", "name": "_sharesAmountToBurn", @@ -448,7 +743,11 @@ }, { "inputs": [ - { "internalType": "address", "name": "_from", "type": "address" }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, { "internalType": "uint256", "name": "_sharesAmountToBurn", @@ -462,8 +761,16 @@ }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "revokeRole", "outputs": [], @@ -472,12 +779,25 @@ }, { "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } ], "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, - { "stateMutability": "payable", "type": "receive" } + { + "stateMutability": "payable", + "type": "receive" + } ] diff --git a/tests/acceptance/test_burner.py b/tests/acceptance/test_burner.py index 31913dde2..95234854f 100644 --- a/tests/acceptance/test_burner.py +++ b/tests/acceptance/test_burner.py @@ -15,5 +15,5 @@ def contract() -> interface.Burner: def test_links(contract): - assert contract.STETH() == contracts.lido - assert contract.TREASURY() == contracts.agent + assert contract.LIDO() == contracts.lido + assert contract.LOCATOR() == contracts.lido_locator diff --git a/tests/regression/test_pause_resume.py b/tests/regression/test_pause_resume.py index 36b21b978..853231bb1 100644 --- a/tests/regression/test_pause_resume.py +++ b/tests/regression/test_pause_resume.py @@ -218,18 +218,15 @@ def test_stopped_lido_cant_deposit(): contracts.lido.deposit(1, 1, "0x", {"from": contracts.deposit_security_module}), -@pytest.mark.usefixtures("stopped_lido") def test_resumed_staking_can_stake(stranger): + contracts.lido.pauseStaking({"from": contracts.agent}) contracts.lido.resumeStaking({"from": contracts.agent}) stranger.transfer(contracts.lido, DEPOSIT_AMOUNT) - @pytest.mark.usefixtures("stopped_lido") def test_resumed_staking_cant_deposit(): - contracts.lido.resumeStaking({"from": contracts.agent}) - - with brownie.reverts("CAN_NOT_DEPOSIT"): - contracts.lido.deposit(1, 1, "0x", {"from": contracts.deposit_security_module}), + with brownie.reverts("CONTRACT_IS_STOPPED"): + contracts.lido.resumeStaking({"from": contracts.agent}), @pytest.mark.usefixtures("stopped_lido") From 7a0cc087e6abb9dfe37f4b6ea23c2c73d62f5fa6 Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 20 Nov 2025 16:42:06 +0300 Subject: [PATCH 041/178] test: fix sanity checks tests --- interfaces/LidoLocator.json | 742 +++++++++++++++---------- tests/regression/test_sanity_checks.py | 6 +- 2 files changed, 452 insertions(+), 296 deletions(-) diff --git a/interfaces/LidoLocator.json b/interfaces/LidoLocator.json index a25962cf3..3c480986e 100644 --- a/interfaces/LidoLocator.json +++ b/interfaces/LidoLocator.json @@ -1,343 +1,497 @@ [ - { - "inputs": [], - "name": "accounting", - "outputs": [ - { + { + "inputs": [ + { + "components": [ + { "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "accountingOracle", - "outputs": [ - { - "internalType": "address", - "name": "", + "name": "accountingOracle", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "burner", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "depositSecurityModule", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "coreComponents", - "outputs": [ - { + }, + { "internalType": "address", "name": "elRewardsVault", "type": "address" - }, - { - "internalType": "address", - "name": "oracleReportSanityChecker", - "type": "address" - }, - { - "internalType": "address", - "name": "stakingRouter", - "type": "address" - }, - { - "internalType": "address", - "name": "treasury", - "type": "address" - }, - { - "internalType": "address", - "name": "withdrawalQueue", - "type": "address" - }, - { - "internalType": "address", - "name": "withdrawalVault", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "depositSecurityModule", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "elRewardsVault", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lazyOracle", - "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": "operatorGrid", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "oracleDaemonConfig", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "lido", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "oracleReportComponents", - "outputs": [ - { + }, + { "internalType": "address", - "name": "accountingOracle", + "name": "oracleReportSanityChecker", "type": "address" - }, - { + }, + { "internalType": "address", - "name": "oracleReportSanityChecker", + "name": "postTokenRebaseReceiver", "type": "address" - }, - { + }, + { "internalType": "address", "name": "burner", "type": "address" - }, - { + }, + { "internalType": "address", - "name": "withdrawalQueue", + "name": "stakingRouter", "type": "address" - }, - { + }, + { "internalType": "address", - "name": "postTokenRebaseReceiver", + "name": "treasury", "type": "address" - }, - { + }, + { "internalType": "address", - "name": "stakingRouter", + "name": "validatorsExitBusOracle", "type": "address" - }, - { + }, + { "internalType": "address", - "name": "vaultHub", + "name": "withdrawalQueue", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "oracleReportSanityChecker", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "withdrawalVault", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "postTokenRebaseReceiver", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "oracleDaemonConfig", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "predepositGuarantee", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "validatorExitDelayVerifier", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "stakingRouter", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "triggerableWithdrawalsGateway", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "treasury", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "accounting", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "validatorsExitBusOracle", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "predepositGuarantee", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "vaultFactory", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "wstETH", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "vaultHub", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "vaultHub", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "withdrawalQueue", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "vaultFactory", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "withdrawalVault", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "lazyOracle", "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "wstETH", - "outputs": [ - { + }, + { "internalType": "address", - "name": "", + "name": "operatorGrid", "type": "address" - } + } ], - "stateMutability": "view", - "type": "function" - } + "internalType": "struct LidoLocator.Config", + "name": "_config", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "accounting", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "accountingOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "burner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "coreComponents", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "depositSecurityModule", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "elRewardsVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lazyOracle", + "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": "operatorGrid", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleDaemonConfig", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleReportComponents", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleReportSanityChecker", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "postTokenRebaseReceiver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "predepositGuarantee", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingRouter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "triggerableWithdrawalsGateway", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "validatorExitDelayVerifier", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "validatorsExitBusOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultFactory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultHub", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalQueue", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "wstETH", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } ] diff --git a/tests/regression/test_sanity_checks.py b/tests/regression/test_sanity_checks.py index 4dca87fba..dbd2bd44f 100644 --- a/tests/regression/test_sanity_checks.py +++ b/tests/regression/test_sanity_checks.py @@ -41,12 +41,14 @@ def first_report(): def test_cant_report_more_validators_than_deposited(): (deposited, clValidators, _) = contracts.lido.getBeaconStat() - with reverts("REPORTED_MORE_DEPOSITED"): + with reverts("IncorrectReportValidators: " + str(deposited + 1) + ", " + str(clValidators) + ", " + str(deposited)): oracle_report(cl_appeared_validators=deposited - clValidators + 1, skip_withdrawals=True, silent=True) def test_validators_cant_decrease(): - with reverts("REPORTED_LESS_VALIDATORS"): + # panic code 0x11 (Arithmetic overflow) + # Brownie sometimes fails to decode panic codes and throws AttributeError + with pytest.raises((Exception, AttributeError)): oracle_report(cl_appeared_validators=-1, skip_withdrawals=True, silent=True) From 4d9aa8c7b4dbf3a822e1ea4d8e2c52ce061aa92a Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 20 Nov 2025 19:06:37 +0300 Subject: [PATCH 042/178] test: fix paused_staking_module_can_reward --- configs/config_mainnet.py | 2 +- tests/regression/test_pause_resume.py | 37 +++++++++++++++++---------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 0cda4b1b0..39fb080f7 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -162,7 +162,7 @@ SIMPLE_DVT_ARAGON_APP_NAME = "simple-dvt" SIMPLE_DVT_ARAGON_APP_ID = "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4" SIMPLE_DVT_MODULE_STUCK_PENALTY_DELAY = 0 -SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 430 +SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 400 SIMPLE_DVT_MODULE_MODULE_FEE_BP = 800 SIMPLE_DVT_MODULE_TREASURY_FEE_BP = 200 SIMPLE_DVT_MODULE_ID = 2 diff --git a/tests/regression/test_pause_resume.py b/tests/regression/test_pause_resume.py index 853231bb1..94156240b 100644 --- a/tests/regression/test_pause_resume.py +++ b/tests/regression/test_pause_resume.py @@ -272,27 +272,37 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.DepositsPaused, {"from": stranger}) (report_tx, _) = oracle_report() - print(report_tx.events["Transfer"]) + # Note: why we use TransferShares event in this test - https://github.com/lidofinance/core/issues/1565 + # print(report_tx.events["TransferShares"]) module_index = 0 simple_dvt_index = 1 csm_index = 2 - if report_tx.events["Transfer"][module_index]["to"] == burner.address: + if report_tx.events["TransferShares"][module_index]["to"] == burner.address: module_index += 1 simple_dvt_index += 1 csm_index += 1 agent_index = module_index + 3 - assert report_tx.events["Transfer"][module_index]["to"] == module_address - assert report_tx.events["Transfer"][module_index]["from"] == ZERO_ADDRESS - assert report_tx.events["Transfer"][agent_index]["to"] == contracts.agent - assert report_tx.events["Transfer"][agent_index]["from"] == ZERO_ADDRESS + assert report_tx.events["TransferShares"][module_index]["to"] == module_address + assert report_tx.events["TransferShares"][module_index]["from"] == ZERO_ADDRESS + assert report_tx.events["TransferShares"][agent_index]["to"] == contracts.agent + assert report_tx.events["TransferShares"][agent_index]["from"] == ZERO_ADDRESS - # the staking modules ids starts from 1, so SDVT has id = 2 + + # the staking modules ids starts from 1 + module_stats = contracts.staking_router.getStakingModule(1) + # module_treasury_fee = module_share / share_pct * treasury_pct + module_treasury_fee = ( + report_tx.events["TransferShares"][module_index]["sharesValue"] + * 100_00 + // module_stats["stakingModuleFee"] + * module_stats["treasuryFee"] + // 100_00 + ) simple_dvt_stats = contracts.staking_router.getStakingModule(2) - # simple_dvt_treasury_fee = sdvt_share / share_pct * treasury_pct simple_dvt_treasury_fee = ( - report_tx.events["Transfer"][simple_dvt_index]["value"] + report_tx.events["TransferShares"][simple_dvt_index]["sharesValue"] * 100_00 // simple_dvt_stats["stakingModuleFee"] * simple_dvt_stats["treasuryFee"] @@ -300,18 +310,19 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): ) csm_stats = contracts.staking_router.getStakingModule(3) csm_treasury_fee = ( - report_tx.events["Transfer"][csm_index]["value"] + report_tx.events["TransferShares"][csm_index]["sharesValue"] * 100_00 // csm_stats["stakingModuleFee"] * csm_stats["treasuryFee"] // 100_00 ) + assert almostEqWithDiff( - report_tx.events["Transfer"][module_index]["value"] + simple_dvt_treasury_fee + csm_treasury_fee, - report_tx.events["Transfer"][agent_index]["value"], + module_treasury_fee + simple_dvt_treasury_fee + csm_treasury_fee, + report_tx.events["TransferShares"][agent_index]["sharesValue"], 100, ) - assert report_tx.events["Transfer"][module_index]["value"] > 0 + assert report_tx.events["TransferShares"][module_index]["sharesValue"] > 0 def test_stopped_staking_module_cant_stake(stranger): From 84017fb76fa2fb7af5f608dac2c160b2dd783b18 Mon Sep 17 00:00:00 2001 From: dry914 Date: Fri, 21 Nov 2025 20:07:01 +0300 Subject: [PATCH 043/178] test: fix a few snapshot tests --- tests/snapshot/test_first_slots.py | 4 ++-- tests/snapshot/test_plain_submit.py | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/snapshot/test_first_slots.py b/tests/snapshot/test_first_slots.py index f1e119966..ea8bce27a 100644 --- a/tests/snapshot/test_first_slots.py +++ b/tests/snapshot/test_first_slots.py @@ -49,8 +49,8 @@ def test_first_slots(sandwich_upgrade: SandwichFn): def skip_slots() -> Sequence[tuple[str, int]]: """Slots that are not checked for equality""" return [ - # reset slot in kernel - (contracts.kernel.address, 0x01), + # adding 9 new easy track factories + (contracts.easy_track.address, 0x05), ] diff --git a/tests/snapshot/test_plain_submit.py b/tests/snapshot/test_plain_submit.py index 3bcb85684..60eac0875 100644 --- a/tests/snapshot/test_plain_submit.py +++ b/tests/snapshot/test_plain_submit.py @@ -45,7 +45,6 @@ def snapshot() -> Dict[str, any]: "allowRecoverability(LDO)": lido.allowRecoverability(LDO_TOKEN), "allowRecoverability(StETH)": lido.allowRecoverability(LIDO), "appId": lido.appId(), - "getOracle()": lido.getOracle(), "getInitializationBlock()": lido.getInitializationBlock(), "symbol": lido.symbol(), "getEVMScriptRegistry": lido.getEVMScriptRegistry(), @@ -80,7 +79,7 @@ def steps() -> Dict[str, Dict[str, any]]: before: Dict[str, Dict[str, any]] = steps() chain.revert() - + execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env) after: Dict[str, Dict[str, any]] = steps() @@ -88,13 +87,13 @@ def steps() -> Dict[str, Dict[str, any]]: expected_diffs = { "canPerform()": ValueChanged(from_val=True, to_val=False), - "getRecoveryVault()": ValueChanged(from_val=contracts.agent.address, to_val=ZERO_ADDRESS), + "getRecoveryVault()": ValueChanged(from_val=contracts.agent.address, to_val=ZERO_ADDRESS), } for step, pair_of_snapshots in dict_zip(before, after).items(): (before, after) = pair_of_snapshots step_diffs[step] = dict_diff(before, after) - + for key in expected_diffs: if key in step_diffs[step] and step_diffs[step][key] == expected_diffs[key]: del step_diffs[step][key] From 4306f1d52bd4df58e0a5e295cb103e05a7ea20a8 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 25 Nov 2025 09:23:11 +0000 Subject: [PATCH 044/178] feat: added sUSDS --- scripts/vote_2025_11_24.py | 151 ------ scripts/vote_2025_12_10.py | 299 ++++++++++++ ...{test_2025_11_24.py => test_2025_12_10.py} | 440 +++++++++++++++++- 3 files changed, 727 insertions(+), 163 deletions(-) delete mode 100644 scripts/vote_2025_11_24.py create mode 100644 scripts/vote_2025_12_10.py rename tests/{test_2025_11_24.py => test_2025_12_10.py} (53%) diff --git a/scripts/vote_2025_11_24.py b/scripts/vote_2025_11_24.py deleted file mode 100644 index d49b7f5a2..000000000 --- a/scripts/vote_2025_11_24.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -# Vote 2025_11_24 - -=== 1. DG PROPOPSAL === -I. raise SDVT stake share limit -1.1. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - -II. Reset Easy Track TRP limit -1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO -1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - -=== NON-DG ITEMS === -III. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig -2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 - -# TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. -""" - -from typing import Dict, List, Tuple - -from utils.finance import make_matic_payout -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 get_deployer_account, get_is_live, get_priority_fee -from utils.mainnet_fork import pass_and_exec_dao_vote -from utils.dual_governance import submit_proposals -from utils.agent import agent_forward -from brownie import interface -from utils.allowed_recipients_registry import ( - unsafe_set_spent_amount, - set_limit_parameters, -) - - -# ============================== Addresses =================================== -ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" -STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" - - -# ============================== Constants =================================== -SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 -SDVT_MODULE_MODULE_FEE_BP = 800 -SDVT_MODULE_TREASURY_FEE_BP = 200 -SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 -SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 - -TRP_PERIOD_DURATION_MONTHS = 12 -TRP_NEW_LIMIT = 15_000_000 * 10**18 -TRP_NEW_SPENT_AMOUNT = 0 - -MATIC_FOR_TRANSFER = 508_106 * 10**18 - - -# ============================= Description ================================== -# TODO -IPFS_DESCRIPTION = "omni nov 2025" - - -# ================================ Main ====================================== -def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: - - staking_router = interface.StakingRouter(STAKING_ROUTER) - - dg_items = [ - agent_forward([ - # 1.1. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - ( - staking_router.address, - staking_router.updateStakingModule.encode_input( - SDVT_MODULE_ID, - SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, - SDVT_MODULE_MODULE_FEE_BP, - SDVT_MODULE_TREASURY_FEE_BP, - SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, - SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, - ), - ), - ]), - agent_forward([ - # 1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), - ]), - agent_forward([ - # 1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - set_limit_parameters( - limit=TRP_NEW_LIMIT, - period_duration_months=TRP_PERIOD_DURATION_MONTHS, - registry_address=ET_TRP_REGISTRY, - ), - ]), - ] - - dg_call_script = submit_proposals([ - (dg_items, "Upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit") - ]) - - vote_desc_items, call_script_items = zip( - ( - "1. Submit a Dual Governance proposal to upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit", - dg_call_script[0] - ), - ( - "2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5", - make_matic_payout( - target_address=LOL_MS, - matic_in_wei=MATIC_FOR_TRANSFER, - reference="Transfer 508,106 MATIC from Treasury to Liquidity Observation Lab (LOL) Multisig", - ), - ) - ) - - 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)) - - desc_ipfs = ( - calculate_vote_ipfs_description(IPFS_DESCRIPTION) - if silent else upload_vote_ipfs_description(IPFS_DESCRIPTION) - ) - - vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list( - create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) - ) - - return vote_id, tx - - -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}.") - - -def start_and_execute_vote_on_fork_manual(): - 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) - print(f"Vote created: {vote_id}.") - pass_and_exec_dao_vote(int(vote_id), step_by_step=True) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py new file mode 100644 index 000000000..9b2a93fd8 --- /dev/null +++ b/scripts/vote_2025_12_10.py @@ -0,0 +1,299 @@ +""" +# Vote 2025_12_10 + +=== 1. DG PROPOPSAL === +I. raise SDVT stake share limit +1.1. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + +II. Reset Easy Track TRP limit +1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO +1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + +=== NON-DG ITEMS === +III. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig +2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 + +# TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. +""" + +from typing import Dict, List, Tuple, NamedTuple + +from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op +from utils.finance import make_matic_payout +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 get_deployer_account, get_is_live, get_priority_fee +from utils.mainnet_fork import pass_and_exec_dao_vote +from utils.dual_governance import submit_proposals +from utils.agent import agent_forward +from utils.permissions import encode_permission_revoke, encode_permission_grant_p +from brownie import interface, ZERO_ADDRESS, convert, web3 +from utils.allowed_recipients_registry import ( + unsafe_set_spent_amount, + set_limit_parameters, +) + + +class TokenLimit(NamedTuple): + address: str + limit: int + + +# ============================== Addresses =================================== +ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" +FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" +ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" +ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" +CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" +ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" + +SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" +USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" +DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" +LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" +LIDO = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + + +# ============================== Constants =================================== +SDVT_MODULE_ID = 2 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 +SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_MODULE_FEE_BP = 800 +SDVT_MODULE_TREASURY_FEE_BP = 200 +SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + +TRP_PERIOD_DURATION_MONTHS = 12 +TRP_NEW_LIMIT = 15_000_000 * 10**18 +TRP_NEW_SPENT_AMOUNT = 0 + +MATIC_FOR_TRANSFER = 508_106 * 10**18 + +def amount_limits() -> List[Param]: + ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) + eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) + steth_limit = TokenLimit(LIDO, 1_000 * (10**18)) + dai_limit = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) + usdc_limit = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) + usdt_limit = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) + susds_limit = TokenLimit(SUSDS_TOKEN, 2_000_000 * (10**18)) + + token_arg_index = 0 + amount_arg_index = 2 + + return [ + # 0: if (1) then (2) else (3) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) + ), + # 1: (_token == stETH) + Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit.address)), + # 2: { return _amount <= 1_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit.limit)), + # + # 3: else if (4) then (5) else (6) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) + ), + # 4: (_token == DAI) + Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit.address)), + # 5: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit.limit)), + # + # 6: else if (7) then (8) else (9) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) + ), + # 7: (_token == LDO) + Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit.address)), + # 8: { return _amount <= 5_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit.limit)), + # + # 9: else if (10) then (11) else (12) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=10, success=11, failure=12), + ), + # 10: (_token == USDC) + Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit.address)), + # 11: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit.limit)), + # + # 12: else if (13) then (14) else (15) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=13, success=14, failure=15), + ), + # 13: (_token == USDT) + Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit.address)), + # 14: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit.limit)), + # + # 15: else if (16) then (17) else (18) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=16, success=17, failure=18), + ), + # 16: (_token == ETH) + Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit.address)), + # 17: { return _amount <= 1000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit.limit)), + # + # 18: else if (19) then (20) else (21) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=19, success=20, failure=21), + ), + # 19: (_token == sUSDS) + Param(token_arg_index, Op.EQ, ArgumentValue(susds_limit.address)), + # 20: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(susds_limit.limit)), + # + # 21: else { return false } + Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), + ] + + +# ============================= Description ================================== +# TODO +IPFS_DESCRIPTION = "omni nov 2025" + + +# ================================ Main ====================================== +def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: + + staking_router = interface.StakingRouter(STAKING_ROUTER) + allowed_tokens_registry = interface.AllowedTokensRegistry(ALLOWED_TOKENS_REGISTRY) + + dg_items = [ + agent_forward([ + # 1.1. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + SDVT_MODULE_ID, + SDVT_MODULE_NEW_TARGET_SHARE_BP, + SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_MODULE_FEE_BP, + SDVT_MODULE_TREASURY_FEE_BP, + SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, + SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), + agent_forward([ + # 1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), + ]), + agent_forward([ + # 1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + set_limit_parameters( + limit=TRP_NEW_LIMIT, + period_duration_months=TRP_PERIOD_DURATION_MONTHS, + registry_address=ET_TRP_REGISTRY, + ), + ]), + ] + + dg_call_script = submit_proposals([ + (dg_items, "Upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit") + ]) + + vote_desc_items, call_script_items = zip( + ( + "1. Submit a Dual Governance proposal to upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit", + dg_call_script[0] + ), + ( + "2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5", + make_matic_payout( + target_address=LOL_MS, + matic_in_wei=MATIC_FOR_TRANSFER, + reference="Transfer 508,106 MATIC from Treasury to Liquidity Observation Lab (LOL) Multisig", + ), + ), + ( + "1. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting", + ( + allowed_tokens_registry.address, allowed_tokens_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING, + ) + ), + ), + ( + "4343. TODO", + (allowed_tokens_registry.address, allowed_tokens_registry.addToken.encode_input(SUSDS_TOKEN)) + ), + ( + "3. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting", + ( + allowed_tokens_registry.address, allowed_tokens_registry.revokeRole.encode_input( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING, + ) + ) + ), + ( + "3. TODO", + encode_permission_revoke( + target_app=FINANCE, + permission_name=CREATE_PAYMENTS_ROLE, + revoke_from=ET_EVM_SCRIPT_EXECUTOR, + ), + ), + ( + "4. TODO", + encode_permission_grant_p( + target_app=FINANCE, + permission_name=CREATE_PAYMENTS_ROLE, + grant_to=ET_EVM_SCRIPT_EXECUTOR, + params=amount_limits(), + ), + ), + ) + + 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)) + + desc_ipfs = ( + calculate_vote_ipfs_description(IPFS_DESCRIPTION) + if silent else upload_vote_ipfs_description(IPFS_DESCRIPTION) + ) + + vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list( + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) + ) + + return vote_id, tx + + +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}.") + + +def start_and_execute_vote_on_fork_manual(): + 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) + print(f"Vote created: {vote_id}.") + pass_and_exec_dao_vote(int(vote_id), step_by_step=True) diff --git a/tests/test_2025_11_24.py b/tests/test_2025_12_10.py similarity index 53% rename from tests/test_2025_11_24.py rename to tests/test_2025_12_10.py index 24df7e1f2..28a37d60e 100644 --- a/tests/test_2025_11_24.py +++ b/tests/test_2025_12_10.py @@ -1,7 +1,9 @@ -from brownie import chain, interface, reverts, accounts +from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 from brownie.network.transaction import TransactionReceipt import pytest +from typing import List, NamedTuple +from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op from utils.test.tx_tracing_helpers import ( group_voting_events_from_receipt, group_dg_events_from_receipt, @@ -29,12 +31,24 @@ validate_set_limit_parameter_event, validate_set_spent_amount_event, ) +from utils.test.event_validators.permission import ( + validate_grant_role_event, + validate_revoke_role_event, + Permission, + validate_permission_grantp_event, + validate_permission_revoke_event, +) +from utils.test.event_validators.allowed_tokens_registry import validate_add_token_event + +class TokenLimit(NamedTuple): + address: str + limit: int # ============================================================================ # ============================== Import vote ================================= # ============================================================================ -from scripts.vote_2025_11_24 import start_vote, get_vote_items +from scripts.vote_2025_12_10 import start_vote, get_vote_items # ============================================================================ @@ -60,13 +74,14 @@ TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" - -# TODO Set variable to None if item is not presented -EXPECTED_VOTE_ID = 194 -EXPECTED_DG_PROPOSAL_ID = 6 -EXPECTED_VOTE_EVENTS_COUNT = 2 -EXPECTED_DG_EVENTS_COUNT = 3 -IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" +FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" +CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" +ACL = "0x9895f0f17cc1d1891b6f18ee0b483b6f221b37bb" +ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" +ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" +LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0xE1f6BaBb445F809B97e3505Ea91749461050F780" +LIDO_LABS_TRUSTED_CALLER = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" +LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY = "0x68267f3D310E9f0FF53a37c141c90B738E1133c2" SDVT_MODULE_ID = 2 SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 @@ -92,6 +107,202 @@ TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC TRP_PERIOD_DURATION_MONTHS = 12 +SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" +USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" +DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" +ALLOWED_TOKENS_BEFORE = 3 +ALLOWED_TOKENS_AFTER = 4 + + +EXPECTED_VOTE_ID = 194 +EXPECTED_DG_PROPOSAL_ID = 6 +EXPECTED_VOTE_EVENTS_COUNT = 7 +EXPECTED_DG_EVENTS_COUNT = 3 +IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" + + +AMOUNT_LIMITS_LEN_BEFORE = 19 +def amount_limits_before() -> List[Param]: + ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) + eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) + steth_limit = TokenLimit(LIDO, 1_000 * (10**18)) + dai_limit = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) + usdc_limit = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) + usdt_limit = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) + + token_arg_index = 0 + amount_arg_index = 2 + + limits = [ + # 0: if (1) then (2) else (3) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) + ), + # 1: (_token == stETH) + Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit.address)), + # 2: { return _amount <= 1_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit.limit)), + # + # 3: else if (4) then (5) else (6) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) + ), + # 4: (_token == DAI) + Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit.address)), + # 5: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit.limit)), + # + # 6: else if (7) then (8) else (9) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) + ), + # 7: (_token == LDO) + Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit.address)), + # 8: { return _amount <= 5_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit.limit)), + # + # 9: else if (10) then (11) else (12) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=10, success=11, failure=12), + ), + # 10: (_token == USDC) + Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit.address)), + # 11: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit.limit)), + # + # 12: else if (13) then (14) else (15) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=13, success=14, failure=15), + ), + # 13: (_token == USDT) + Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit.address)), + # 14: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit.limit)), + # + # 15: else if (16) then (17) else (18) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=16, success=17, failure=18), + ), + # 16: (_token == ETH) + Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit.address)), + # 17: { return _amount <= 1000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit.limit)), + # + # 18: else { return false } + Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), + ] + + assert len(limits) == AMOUNT_LIMITS_LEN_BEFORE + + return limits + + +AMOUNT_LIMITS_LEN_AFTER = 22 +ldo_limit_after = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) +eth_limit_after = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) +steth_limit_after = TokenLimit(LIDO, 1_000 * (10**18)) +dai_limit_after = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) +usdc_limit_after = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) +usdt_limit_after = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) +susds_limit_after = TokenLimit(SUSDS_TOKEN, 2_000_000 * (10**18)) +def amount_limits_after() -> List[Param]: + + token_arg_index = 0 + amount_arg_index = 2 + + limits = [ + # 0: if (1) then (2) else (3) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) + ), + # 1: (_token == stETH) + Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit_after.address)), + # 2: { return _amount <= 1_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit_after.limit)), + # + # 3: else if (4) then (5) else (6) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) + ), + # 4: (_token == DAI) + Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit_after.address)), + # 5: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit_after.limit)), + # + # 6: else if (7) then (8) else (9) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) + ), + # 7: (_token == LDO) + Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit_after.address)), + # 8: { return _amount <= 5_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit_after.limit)), + # + # 9: else if (10) then (11) else (12) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=10, success=11, failure=12), + ), + # 10: (_token == USDC) + Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit_after.address)), + # 11: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit_after.limit)), + # + # 12: else if (13) then (14) else (15) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=13, success=14, failure=15), + ), + # 13: (_token == USDT) + Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit_after.address)), + # 14: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit_after.limit)), + # + # 15: else if (16) then (17) else (18) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=16, success=17, failure=18), + ), + # 16: (_token == ETH) + Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit_after.address)), + # 17: { return _amount <= 1000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit_after.limit)), + # + # 18: else if (19) then (20) else (21) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=19, success=20, failure=21), + ), + # 19: (_token == sUSDS) + Param(token_arg_index, Op.EQ, ArgumentValue(susds_limit_after.address)), + # 20: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(susds_limit_after.limit)), + # + # 21: else { return false } + Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), + ] + + # Verify that the first part of the after_limits matches the before_limits + for i in range(AMOUNT_LIMITS_LEN_BEFORE - 1): + assert limits[i].id == amount_limits_before()[i].id + assert limits[i].op.value == amount_limits_before()[i].op.value + assert limits[i].value == amount_limits_before()[i].value + + assert len(limits) == AMOUNT_LIMITS_LEN_AFTER + + return limits + @pytest.fixture(scope="module") def dual_governance_proposal_calls(): @@ -152,6 +363,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g matic_token = interface.ERC20(MATIC_TOKEN) staking_router = interface.StakingRouter(STAKING_ROUTER) et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) + acl = interface.ACL(ACL) + allowed_tokens_registry = interface.AllowedTokensRegistry(ALLOWED_TOKENS_REGISTRY) # ========================================================================= @@ -181,12 +394,43 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # TODO add before voting checks - # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 + # Item 2 matic_treasury_balance_before = matic_token.balanceOf(agent.address) assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE matic_labs_balance_before = matic_token.balanceOf(LOL_MS) assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE + # Items 3,5 + assert not allowed_tokens_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING + ) + + # Item 4 + assert not allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_before = allowed_tokens_registry.getAllowedTokens() + assert len(allowed_tokens_before) == ALLOWED_TOKENS_BEFORE + assert allowed_tokens_before[0] == DAI_TOKEN + assert allowed_tokens_before[1] == USDT_TOKEN + assert allowed_tokens_before[2] == USDC_TOKEN + + # Items 6,7 + assert acl.getPermissionParamsLength( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)) + ) == AMOUNT_LIMITS_LEN_BEFORE + for i in range(AMOUNT_LIMITS_LEN_BEFORE): + id, op, val = acl.getPermissionParam( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)), + i + ) + assert id == amount_limits_before()[i].id + assert op == amount_limits_before()[i].op.value + assert val == amount_limits_before()[i].value + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH @@ -200,7 +444,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # TODO add after voting tests - # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 + # Item 2 matic_treasury_balance_after = matic_token.balanceOf(agent.address) assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER matic_labs_balance_after = matic_token.balanceOf(LOL_MS) @@ -210,6 +454,39 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 + # Items 3,5 + assert not allowed_tokens_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING + ) + + # Item 4 + assert allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_before = allowed_tokens_registry.getAllowedTokens() + assert len(allowed_tokens_before) == ALLOWED_TOKENS_AFTER + assert allowed_tokens_before[0] == DAI_TOKEN + assert allowed_tokens_before[1] == USDT_TOKEN + assert allowed_tokens_before[2] == USDC_TOKEN + assert allowed_tokens_before[3] == SUSDS_TOKEN + + # Items 6,7 + assert acl.getPermissionParamsLength( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)) + ) == AMOUNT_LIMITS_LEN_AFTER + for i in range(AMOUNT_LIMITS_LEN_AFTER): + id, op, val = acl.getPermissionParam( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)), + i + ) + assert id == amount_limits_after()[i].id + assert op == amount_limits_after()[i].op.value + assert val == amount_limits_after()[i].value + + assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT if EXPECTED_DG_PROPOSAL_ID is not None: @@ -226,7 +503,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g ) # TODO validate all other voting events - validate_token_payout_event( event=vote_events[1], p=Payout( @@ -237,6 +513,60 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g is_steth=False, emitted_by=AGENT ) + validate_grant_role_event( + events=vote_events[2], + role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), + grant_to=VOTING, + sender=VOTING, + emitted_by=ALLOWED_TOKENS_REGISTRY, + ) + validate_add_token_event( + event=vote_events[3], + token=SUSDS_TOKEN, + emitted_by=ALLOWED_TOKENS_REGISTRY + ) + validate_revoke_role_event( + events=vote_events[4], + role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), + revoke_from=VOTING, + sender=VOTING, + emitted_by=ALLOWED_TOKENS_REGISTRY, + ) + validate_permission_revoke_event( + event=vote_events[5], + p=Permission( + app=FINANCE, + entity=ET_EVM_SCRIPT_EXECUTOR, + role=web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + ), + emitted_by=ACL, + ) + validate_permission_grantp_event( + event=vote_events[6], + p=Permission( + app=FINANCE, + entity=ET_EVM_SCRIPT_EXECUTOR, + role=web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + ), + params=amount_limits_after(), + emitted_by=ACL, + ) + + # ======================================================================= + # =========================== Scenario checks =========================== + # ======================================================================= + + # check ET limits + #et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_SANDBOX_STABLES_LIMIT * 10**18) + #et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_SANDBOX_STABLES_LIMIT * 10**6) + #et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_SANDBOX_STABLES_LIMIT * 10**18) + #et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_SANDBOX_STABLES_LIMIT * 10**6) + + # check Finance limits + #finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18) + #finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6) + #finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18) + #finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6) if EXPECTED_DG_PROPOSAL_ID is not None: @@ -409,3 +739,89 @@ def trp_limit_test(stranger): ) chain.revert() + +def et_limit_test(stranger, token, max_spend_at_once, to_spend): + + easy_track = interface.EasyTrack(EASY_TRACK) + trusted_caller_account = accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True) + + chain.snapshot() + + # check that there is no way to spend more then expected + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [to_spend + 1], + stranger, + ) + + # spend all step by step + while to_spend > 0: + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [min(max_spend_at_once, to_spend)], + stranger, + ) + to_spend -= min(max_spend_at_once, to_spend) + + # make sure there is nothing left so that you can't spend anymore + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [1], + stranger, + ) + + chain.revert() + + +def finance_limit_test(stranger, token, to_spend, decimals): + + easy_track = interface.EasyTrack(EASY_TRACK) + trusted_caller_account = accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True) + + chain.snapshot() + + # for Finance limit check - we first raise ET limits to 2 x finance_limit to be able to spend via Finance + interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).setLimitParameters( + (to_spend / (10**decimals) * 10**18) * 2, # 2 x finance_limit + 3, # 3 months + {"from": AGENT} + ) + + # check that there is no way to spend more then expected + with reverts("APP_AUTH_FAILED"): + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [to_spend + 1], + stranger, + ) + + # spend the allowed balance + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [to_spend], + stranger, + ) + + chain.revert() \ No newline at end of file From e04652512ce921f707ddfb043f14a542717aa121 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 25 Nov 2025 18:19:06 +0300 Subject: [PATCH 045/178] tests: fix snapshot tests --- tests/acceptance/test_locator.py | 2 +- tests/regression/test_neg_rebase_sanity_checks.py | 2 ++ .../regression/test_oracle_report_with_notifier.py | 2 +- tests/regression/test_permissions.py | 13 ++++++++++--- tests/snapshot/test_lido_snapshot.py | 13 +++++++++---- utils/config.py | 8 ++++++++ 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/tests/acceptance/test_locator.py b/tests/acceptance/test_locator.py index 18b1b4d39..8a9d627ba 100644 --- a/tests/acceptance/test_locator.py +++ b/tests/acceptance/test_locator.py @@ -21,7 +21,7 @@ def test_addresses(contract): assert contract.elRewardsVault() == contracts.execution_layer_rewards_vault assert contract.lido() == contracts.lido assert contract.oracleReportSanityChecker() == contracts.oracle_report_sanity_checker - assert contract.postTokenRebaseReceiver() == "0x0000000000000000000000000000000000000000" # TODO contracts.token_rate_notifier + assert contract.postTokenRebaseReceiver() == contracts.token_rate_notifier assert contract.burner() == contracts.burner assert contract.stakingRouter() == contracts.staking_router assert contract.treasury() == contracts.agent diff --git a/tests/regression/test_neg_rebase_sanity_checks.py b/tests/regression/test_neg_rebase_sanity_checks.py index 5f859864b..e7209ea2e 100644 --- a/tests/regression/test_neg_rebase_sanity_checks.py +++ b/tests/regression/test_neg_rebase_sanity_checks.py @@ -87,6 +87,8 @@ def test_blocked_huge_negative_rebase(oracle_report_sanity_checker): locator = contracts.lido_locator assert oracle_report_sanity_checker.address == locator.oracleReportSanityChecker() + oracle_report() + # Advance the chain 60 days more without accounting oracle reports # The idea is to simplify the calculation of the exited validators for 18 and 54 days ago chain.sleep(60 * 24 * 60 * 60) diff --git a/tests/regression/test_oracle_report_with_notifier.py b/tests/regression/test_oracle_report_with_notifier.py index 95347d691..44574f3d5 100644 --- a/tests/regression/test_oracle_report_with_notifier.py +++ b/tests/regression/test_oracle_report_with_notifier.py @@ -8,7 +8,7 @@ WST_ETH = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" -L1_TOKEN_RATE_NOTIFIER = "0xe6793B9e4FbA7DE0ee833F9D02bba7DB5EB27823" +L1_TOKEN_RATE_NOTIFIER = "0xe6793B9e4FbA7DE0ee833F9D02bba7DB5EB27823" # TODO fix after mainnet deployment L1_CROSS_DOMAIN_MESSENGER = "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1" L2_TOKEN_RATE_ORACLE = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" diff --git a/tests/regression/test_permissions.py b/tests/regression/test_permissions.py index ff216ec32..84b85122f 100644 --- a/tests/regression/test_permissions.py +++ b/tests/regression/test_permissions.py @@ -44,6 +44,7 @@ WITHDRAWAL_QUEUE, BURNER, LIDO_LOCATOR, + LEGACY_ORACLE, SIMPLE_DVT, CSM_ADDRESS, CS_ACCOUNTING_ADDRESS, @@ -82,8 +83,8 @@ def protocol_permissions(): "type": "CustomApp", "roles": { "DEFAULT_ADMIN_ROLE": [contracts.agent], - "REQUEST_BURN_MY_STETH_ROLE": [contracts.agent], - "REQUEST_BURN_SHARES_ROLE": [contracts.lido, contracts.node_operators_registry, contracts.simple_dvt, contracts.csm.accounting()], + "REQUEST_BURN_MY_STETH_ROLE": [], + "REQUEST_BURN_SHARES_ROLE": [contracts.accounting, contracts.csm.accounting()], }, }, STAKING_ROUTER: { @@ -98,7 +99,7 @@ def protocol_permissions(): "STAKING_MODULE_MANAGE_ROLE": [contracts.agent], "REPORT_EXITED_VALIDATORS_ROLE": [contracts.accounting_oracle], "UNSAFE_SET_EXITED_VALIDATORS_ROLE": [], - "REPORT_REWARDS_MINTED_ROLE": [contracts.lido], + "REPORT_REWARDS_MINTED_ROLE": [contracts.accounting], "REPORT_VALIDATOR_EXITING_STATUS_ROLE": [contracts.validator_exit_verifier], "REPORT_VALIDATOR_EXIT_TRIGGERED_ROLE": [contracts.triggerable_withdrawals_gateway], }, @@ -319,6 +320,12 @@ def protocol_permissions(): "STAKING_ROUTER_ROLE": [STAKING_ROUTER, EASYTRACK_EVMSCRIPT_EXECUTOR], }, }, + LEGACY_ORACLE: { + "contract_name": "LegacyOracle", + "contract": contracts.legacy_oracle, + "type": "AragonApp", + "roles": {}, + }, CSM_ADDRESS: { "contract_name": "CSModule", "contract": contracts.csm, diff --git a/tests/snapshot/test_lido_snapshot.py b/tests/snapshot/test_lido_snapshot.py index abfd36996..91361d7d5 100644 --- a/tests/snapshot/test_lido_snapshot.py +++ b/tests/snapshot/test_lido_snapshot.py @@ -32,10 +32,17 @@ class Frame(TypedDict): UINT256_MAX = 2**256 - 1 _1ETH = Wei(10**18) +ZERO_BYTES32 = b'\x00' * 32 + EXPECTED_SNAPSHOT_DIFFS: dict[str, Any] = { "canPerform()": (True, False), - "getRecoveryVault": (AGENT, ZERO_ADDRESS) + "getRecoveryVault": (AGENT, ZERO_ADDRESS), + "lido.Lido.beaconBalance": ZERO_BYTES32, + "lido.Lido.beaconValidators": ZERO_BYTES32, + "lido.Lido.bufferedEther": ZERO_BYTES32, + "lido.Lido.depositedValidators": ZERO_BYTES32, + "lido.StETH.totalShares": ZERO_BYTES32, } def test_lido_no_changes_in_views(sandwich_upgrade: SandwichFn): @@ -232,7 +239,7 @@ def get_actions(from_address: Account | None = None): return ( _call(lido.pauseStaking, {"from": from_address}), _call(lido.stop, {"from": from_address}), - _call(lido.resumeStaking, {"from": from_address}), + _call(lido.resume, {"from": from_address}), _call(lido.pauseStaking, {"from": from_address}), _call(lido.removeStakingLimit, {"from": from_address}), _call(lido.resumeStaking, {"from": from_address}), @@ -245,7 +252,6 @@ def get_actions(from_address: Account | None = None): ), _call(lido.pauseStaking, {"from": from_address}), _call(lido.setStakingLimit, 17, 3, {"from": from_address}), - _call(lido.resume, {"from": from_address}), _call(lido.stop, {"from": from_address}), ) @@ -294,7 +300,6 @@ def _snap(): "getCurrentStakeLimit": lido.getCurrentStakeLimit(), "getFeeDistribution": lido.getFeeDistribution(), "getFee": lido.getFee(), - "getOracle": lido.getOracle(), "getStakeLimitFullInfo": lido.getStakeLimitFullInfo(), "getTotalELRewardsCollected": lido.getTotalELRewardsCollected(), "getTotalShares": lido.getTotalShares(), diff --git a/utils/config.py b/utils/config.py index 8a6d4bbe7..897262b1f 100644 --- a/utils/config.py +++ b/utils/config.py @@ -258,6 +258,14 @@ def cs_strikes(self) -> interface.CSStrikes: def sandbox(self) -> interface.SimpleDVT: return interface.Sandbox(SANDBOX) + @property + def legacy_oracle(self) -> interface.LegacyOracle: + return interface.LegacyOracle(LEGACY_ORACLE) + + @property + def token_rate_notifier(self) -> interface.TokenRateNotifier: + return interface.LegacyOracle(L1_TOKEN_RATE_NOTIFIER) + @property def deposit_security_module_v1(self) -> interface.DepositSecurityModule: return interface.DepositSecurityModuleV1(DEPOSIT_SECURITY_MODULE_V1) From fd93fb2a62370b6e75856b772d22884df4f76f96 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 25 Nov 2025 18:38:44 +0300 Subject: [PATCH 046/178] test: clean v3 vote test --- ...v3.py => upgrade_2025_12_10_mainnet_v3.py} | 2 +- tests/test_2025_11_24_mainnet_v3.py | 414 ------------------ tests/test_2025_12_10_mainnet_v3.py | 174 ++++++++ 3 files changed, 175 insertions(+), 415 deletions(-) rename scripts/{upgrade_2025_11_24_mainnet_v3.py => upgrade_2025_12_10_mainnet_v3.py} (96%) delete mode 100644 tests/test_2025_11_24_mainnet_v3.py create mode 100644 tests/test_2025_12_10_mainnet_v3.py diff --git a/scripts/upgrade_2025_11_24_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py similarity index 96% rename from scripts/upgrade_2025_11_24_mainnet_v3.py rename to scripts/upgrade_2025_12_10_mainnet_v3.py index b0d4bb707..a5ede207e 100644 --- a/scripts/upgrade_2025_11_24_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -45,7 +45,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: (dg_items, "TODO DG proposal description")# TODO take from next-vote ]) - vote_desc_items.append("TODO DG submission description") + vote_desc_items.append("TODO DG submission description") # TODO looks like this description is not used # TODO take from next-vote call_script_items.append(dg_call_script[0]) # receive non-DG vote items from omnibus contract diff --git a/tests/test_2025_11_24_mainnet_v3.py b/tests/test_2025_11_24_mainnet_v3.py deleted file mode 100644 index 5f78d626c..000000000 --- a/tests/test_2025_11_24_mainnet_v3.py +++ /dev/null @@ -1,414 +0,0 @@ -from brownie import chain, interface, reverts, accounts -from brownie.network.transaction import TransactionReceipt -import pytest - -from utils.test.tx_tracing_helpers import ( - group_voting_events_from_receipt, - group_dg_events_from_receipt, - count_vote_items_by_events, - display_voting_events, - display_dg_events -) -from utils.test.easy_track_helpers import create_and_enact_payment_motion -from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem -from utils.evm_script import encode_call_script -from utils.voting import find_metadata_by_vote_id -from utils.ipfs import get_lido_vote_cid_from_str -from utils.dual_governance import PROPOSAL_STATUS -from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event -from utils.allowed_recipients_registry import ( - unsafe_set_spent_amount, - set_limit_parameters, -) -from utils.agent import agent_forward -from utils.test.event_validators.payout import ( - validate_token_payout_event, - Payout, -) -from utils.test.event_validators.allowed_recipients_registry import ( - validate_set_limit_parameter_event, - validate_set_spent_amount_event, -) - - -# ============================================================================ -# ============================== Import vote ================================= -# ============================================================================ -from scripts.upgrade_2025_11_24_mainnet_v3 import start_vote, get_vote_items - - -# ============================================================================ -# ============================== Constants =================================== -# ============================================================================ -# TODO list all contract addresses used in tests - do not use imports from config! -# NOTE: these addresses might have a different value on other chains - -VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" -AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" -EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" -DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" -DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" -ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" -STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -LIDO_LABS_MS = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" -DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" -ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" -DEPOSIT_SECURITY_MODULE = "0xffa96d84def2ea035c7ab153d8b991128e3d72fd" -LIDO = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" -SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" -EASY_TRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" -TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" -TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" -LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" - -# TODO Set variable to None if item is not presented -EXPECTED_VOTE_ID = 194 -EXPECTED_DG_PROPOSAL_ID = 6 -EXPECTED_VOTE_EVENTS_COUNT = 2 -EXPECTED_DG_EVENTS_COUNT = 3 -IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" - -SDVT_MODULE_ID = 2 -SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 -SDVT_MODULE_MODULE_FEE_BP = 800 -SDVT_MODULE_TREASURY_FEE_BP = 200 -SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 -SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 -SDVT_MODULE_NAME = "SimpleDVT" - -MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" -MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 -MATIC_IN_TREASURY_AFTER = 165_781_175_837_137_177 -MATIC_IN_LIDO_LABS_BEFORE = 0 -MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 - -TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 -TRP_ALREADY_SPENT_BEFORE = 2_676_801 * 10**18 -TRP_ALREADY_SPENT_AFTER = 0 -TRP_LIMIT_AFTER = 15_000_000 * 10**18 -TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC -TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC -TRP_PERIOD_DURATION_MONTHS = 12 - - -@pytest.fixture(scope="module") -def dual_governance_proposal_calls(): - - staking_router = interface.StakingRouter(STAKING_ROUTER) - - # Create all the dual governance calls that match the voting script - dg_items = [ - agent_forward([ - unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), - ]), - agent_forward([ - set_limit_parameters( - limit=TRP_LIMIT_AFTER, - period_duration_months=TRP_PERIOD_DURATION_MONTHS, - registry_address=ET_TRP_REGISTRY, - ), - ]), - agent_forward([ - ( - staking_router.address, - staking_router.updateStakingModule.encode_input( - SDVT_MODULE_ID, - SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, - SDVT_MODULE_MODULE_FEE_BP, - SDVT_MODULE_TREASURY_FEE_BP, - SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, - SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, - ), - ), - ]) - ] - - # Convert each dg_item to the expected format - proposal_calls = [] - for dg_item in dg_items: - target, data = dg_item # agent_forward returns (target, data) - proposal_calls.append({ - "target": target, - "value": 0, - "data": data - }) - - return proposal_calls - - -def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): - - # ======================================================================= - # ========================= Arrange variables =========================== - # ======================================================================= - voting = interface.Voting(VOTING) - agent = interface.Agent(AGENT) - timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) - dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) - matic_token = interface.ERC20(MATIC_TOKEN) - staking_router = interface.StakingRouter(STAKING_ROUTER) - et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) - - - # ========================================================================= - # ======================== Identify or Create vote ======================== - # ========================================================================= - if vote_ids_from_env: - vote_id = vote_ids_from_env[0] - if EXPECTED_VOTE_ID is not None: - assert vote_id == EXPECTED_VOTE_ID - elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: - vote_id = EXPECTED_VOTE_ID - else: - vote_id, _ = start_vote({"from": ldo_holder}, silent=True) - - _, call_script_items = get_vote_items() - onchain_script = voting.getVote(vote_id)["script"] - assert onchain_script == encode_call_script(call_script_items) - - - # ========================================================================= - # ============================= Execute Vote ============================== - # ========================================================================= - is_executed = voting.getVote(vote_id)["executed"] - if not is_executed: - # ======================================================================= - # ========================= Before voting checks ======================== - # ======================================================================= - # TODO add before voting checks - - # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F - matic_treasury_balance_before = matic_token.balanceOf(agent.address) - assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE - matic_labs_balance_before = matic_token.balanceOf(LIDO_LABS_MS) - assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE - - - assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH - - vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) - display_voting_events(vote_tx) - vote_events = group_voting_events_from_receipt(vote_tx) - - - # ======================================================================= - # ========================= After voting checks ========================= - # ======================================================================= - # TODO add after voting tests - - # # 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Lido Labs Foundation 0x95B521B4F55a447DB89f6a27f951713fC2035f3F - # matic_treasury_balance_after = matic_token.balanceOf(agent.address) - # assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER - # matic_labs_balance_after = matic_token.balanceOf(LIDO_LABS_MS) - # assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER - # # make sure Lido Labs can actually spend the received MATIC - # matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LIDO_LABS_MS}) - # assert matic_token.balanceOf(LIDO_LABS_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 - # assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 - - # assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT - # assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT - # if EXPECTED_DG_PROPOSAL_ID is not None: - # assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - - # # Validate DG Proposal Submit event - # validate_dual_governance_submit_event( - # vote_events[0], - # proposal_id=EXPECTED_DG_PROPOSAL_ID, - # proposer=VOTING, - # executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - # metadata="TODO DG proposal description", - # proposal_calls=dual_governance_proposal_calls, - # emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], - # ) - - # # TODO validate all other voting events - - # validate_token_payout_event( - # event=vote_events[1], - # p=Payout( - # token_addr=MATIC_TOKEN, - # from_addr=AGENT, - # to_addr=LIDO_LABS_MS, - # amount=MATIC_IN_LIDO_LABS_AFTER), - # is_steth=False, - # emitted_by=AGENT - # ) - - - # if EXPECTED_DG_PROPOSAL_ID is not None: - # details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) - # if details["status"] != PROPOSAL_STATUS["executed"]: - # # ========================================================================= - # # ================== DG before proposal executed checks =================== - # # ========================================================================= - # # TODO add DG before proposal executed checks - - # # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - # # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - # trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() - # trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() - # assert trp_limit_before == TRP_LIMIT_BEFORE - # assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS - # assert trp_already_spent_amount_before == TRP_ALREADY_SPENT_BEFORE - # assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - TRP_ALREADY_SPENT_BEFORE - # assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP - # assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP - - # # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - # sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) - # assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP - # assert sdvt_module_before['id'] == SDVT_MODULE_ID - # assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP - # assert sdvt_module_before['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP - # assert sdvt_module_before['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP - # assert sdvt_module_before['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK - # assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - # assert sdvt_module_before['name'] == SDVT_MODULE_NAME - - - # if details["status"] == PROPOSAL_STATUS["submitted"]: - # chain.sleep(timelock.getAfterSubmitDelay() + 1) - # dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - - # # if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: - # # chain.sleep(timelock.getAfterScheduleDelay() + 1) - # # dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - # # display_dg_events(dg_tx) - # # dg_events = group_dg_events_from_receipt( - # # dg_tx, - # # timelock=EMERGENCY_PROTECTED_TIMELOCK, - # # admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - # # ) - # # assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT - # # assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT - - # # # TODO validate all DG events - - # # validate_set_spent_amount_event( - # # dg_events[0], - # # new_spent_amount=0, - # # emitted_by=ET_TRP_REGISTRY, - # # is_dg_event=True, - # # ) - - # # validate_set_limit_parameter_event( - # # dg_events[1], - # # limit=TRP_LIMIT_AFTER, - # # period_duration_month=TRP_PERIOD_DURATION_MONTHS, - # # period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, - # # emitted_by=ET_TRP_REGISTRY, - # # is_dg_event=True, - # # ) - - # # validate_staking_module_update_event( - # # event=dg_events[2], - # # module_item=StakingModuleItem( - # # id=SDVT_MODULE_ID, - # # name=SDVT_MODULE_NAME, - # # address=None, - # # target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, - # # module_fee=SDVT_MODULE_MODULE_FEE_BP, - # # treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, - # # priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), - # # emitted_by=STAKING_ROUTER, - # # is_dg_event=True - # # ) - - # # ========================================================================= - # # ==================== After DG proposal executed checks ================== - # # ========================================================================= - # # TODO add DG after proposal executed checks - - # # 1.1. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - # # 1.2. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months - # trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() - # trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() - # # assert trp_limit_after == TRP_LIMIT_AFTER - # # assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS - # # assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER - # # assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER - # # assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP - # # assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - - # # # 1.3. Increase SDVT (MODULE_ID = 2) share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 - # # sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) - # # assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP - # # assert sdvt_module_after['id'] == SDVT_MODULE_ID - # # assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP - # # assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP - # # assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP - # # assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK - # # assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - # # assert sdvt_module_after['name'] == SDVT_MODULE_NAME - # # # additional checks to make sure no other fields were changed - # # assert sdvt_module_after['id'] == sdvt_module_before['id'] - # # assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] - # # assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] - # # assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] - # # assert sdvt_module_after['status'] == sdvt_module_before['status'] - # # assert sdvt_module_after['name'] == sdvt_module_before['name'] - # # assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] - # # assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] - # # assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] - # # assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] - # # assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] - # # assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] - # # assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) - # # assert len(sdvt_module_after.items()) == 13 - - # # # additional test for TRP ET factory behavior after the vote - # # trp_limit_test(stranger) - - -def trp_limit_test(stranger): - - easy_track = interface.EasyTrack(EASY_TRACK) - ldo_token = interface.ERC20(LDO_TOKEN) - to_spend = TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER - max_spend_at_once = 5_000_000 * 10**18 - trp_committee_account = accounts.at(TRP_COMMITTEE, force=True) - - chain.snapshot() - - # check that there is no way to spend more then expected - with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): - create_and_enact_payment_motion( - easy_track, - TRP_COMMITTEE, - TRP_TOP_UP_EVM_SCRIPT_FACTORY, - ldo_token, - [trp_committee_account], - [to_spend + 1], - stranger, - ) - - # spend all step by step - while to_spend > 0: - create_and_enact_payment_motion( - easy_track, - TRP_COMMITTEE, - TRP_TOP_UP_EVM_SCRIPT_FACTORY, - ldo_token, - [trp_committee_account], - [min(max_spend_at_once, to_spend)], - stranger, - ) - to_spend -= min(max_spend_at_once, to_spend) - - # make sure there is nothing left so that you can't spend anymore - with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): - create_and_enact_payment_motion( - easy_track, - TRP_COMMITTEE, - TRP_TOP_UP_EVM_SCRIPT_FACTORY, - ldo_token, - [trp_committee_account], - [1], - stranger, - ) - - chain.revert() diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py new file mode 100644 index 000000000..636495edb --- /dev/null +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -0,0 +1,174 @@ +from brownie import chain, interface +from brownie.network.transaction import TransactionReceipt +import pytest + +from utils.test.tx_tracing_helpers import ( + group_voting_events_from_receipt, + group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events +) +from utils.evm_script import encode_call_script +from utils.voting import find_metadata_by_vote_id +from utils.ipfs import get_lido_vote_cid_from_str +from utils.dual_governance import PROPOSAL_STATUS +from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event + + +# ============================================================================ +# ============================== Import vote ================================= +# ============================================================================ +from scripts.upgrade_2025_12_10_mainnet_v3 import start_vote, get_vote_items + + +# ============================================================================ +# ============================== Constants =================================== +# ============================================================================ +# TODO list all contract addresses used in tests - do not use imports from config! +# NOTE: these addresses might have a different value on other chains + +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" +EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" +DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" + +# TODO Set variable to None if item is not presented +EXPECTED_VOTE_ID = 194 +EXPECTED_DG_PROPOSAL_ID = 6 +EXPECTED_VOTE_EVENTS_COUNT = 10 +EXPECTED_DG_EVENTS_COUNT = 17 +IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" + + +@pytest.fixture(scope="module") +def dual_governance_proposal_calls(): + # TODO Create all the dual governance calls that match the voting script + dg_items = [ + # # TODO 1.1. DG voting item 1 description + # agent_forward([ + # (dg_item_address_1, dg_item_encoded_input_1) + # ]), + # # TODO 1.2. DG voting item 2 description + # agent_forward([ + # (dg_item_address_2, dg_item_encoded_input_2) + # ]), + ] + + # Convert each dg_item to the expected format + proposal_calls = [] + for dg_item in dg_items: + target, data = dg_item # agent_forward returns (target, data) + proposal_calls.append({ + "target": target, + "value": 0, + "data": data + }) + + return proposal_calls + + +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + voting = interface.Voting(VOTING) + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + + + # ========================================================================= + # ======================== Identify or Create vote ======================== + # ========================================================================= + if vote_ids_from_env: + vote_id = vote_ids_from_env[0] + if EXPECTED_VOTE_ID is not None: + assert vote_id == EXPECTED_VOTE_ID + elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: + vote_id = EXPECTED_VOTE_ID + else: + vote_id, _ = start_vote({"from": ldo_holder}, silent=True) + + _, call_script_items = get_vote_items() + onchain_script = voting.getVote(vote_id)["script"] + assert onchain_script == encode_call_script(call_script_items) + + + # ========================================================================= + # ============================= Execute Vote ============================== + # ========================================================================= + is_executed = voting.getVote(vote_id)["executed"] + if not is_executed: + # ======================================================================= + # ========================= Before voting checks ======================== + # ======================================================================= + # TODO add before voting checks + + + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH + + vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) + display_voting_events(vote_tx) + vote_events = group_voting_events_from_receipt(vote_tx) + + + # ======================================================================= + # ========================= After voting checks ========================= + # ======================================================================= + # TODO add after voting tests + + + assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT + assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT + if EXPECTED_DG_PROPOSAL_ID is not None: + assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() + + # TODO Validate DG Proposal Submit event + # validate_dual_governance_submit_event( + # vote_events[0], + # proposal_id=EXPECTED_DG_PROPOSAL_ID, + # proposer=VOTING, + # executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + # metadata="TODO DG proposal description", + # proposal_calls=dual_governance_proposal_calls, + # emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], + # ) + + # TODO validate all other voting events + + + if EXPECTED_DG_PROPOSAL_ID is not None: + details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) + if details["status"] != PROPOSAL_STATUS["executed"]: + # ========================================================================= + # ================== DG before proposal executed checks =================== + # ========================================================================= + # TODO add DG before proposal executed checks + + + if details["status"] == PROPOSAL_STATUS["submitted"]: + chain.sleep(timelock.getAfterSubmitDelay() + 1) + dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + + if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: + chain.sleep(timelock.getAfterScheduleDelay() + 1) + dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + display_dg_events(dg_tx) + dg_events = group_dg_events_from_receipt( + dg_tx, + timelock=EMERGENCY_PROTECTED_TIMELOCK, + admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + ) + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + + # TODO validate all DG events + + + # ========================================================================= + # ==================== After DG proposal executed checks ================== + # ========================================================================= + # TODO add DG after proposal executed checks From 38d0e9a52f32c3b7fd4b75b99c92f520189a4f1d Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 25 Nov 2025 19:24:51 +0300 Subject: [PATCH 047/178] chore: voting script --- scripts/upgrade_2025_12_10_mainnet_v3.py | 18 ++++++++++-------- tests/test_2025_12_10_mainnet_v3.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index a5ede207e..cef0e83c8 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -26,7 +26,9 @@ # ============================= Description ================================== # TODO -IPFS_DESCRIPTION = "omni nov 2025" +IPFS_DESCRIPTION = "omni dec 2025" +DG_PROPOSAL_DESCRIPTION = "TODO DG proposal description" +DG_SUBMISSION_DESCRIPTION = "TODO DG submission description" # ================================ Main ====================================== @@ -34,21 +36,21 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: vote_desc_items = [] call_script_items = [] - # receive DG vote items from omnibus contract + # 1. receive DG vote items from omnibus contract contract_dg_items = interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).getVoteItems() dg_items = [] - for desc, call_script in contract_dg_items: - dg_items.append((call_script[0], '0x' + call_script[1].hex())) + for _, call_script in contract_dg_items: + dg_items.append((call_script[0], call_script[1].hex())) dg_call_script = submit_proposals([ - (dg_items, "TODO DG proposal description")# TODO take from next-vote + (dg_items, DG_PROPOSAL_DESCRIPTION) ]) - vote_desc_items.append("TODO DG submission description") # TODO looks like this description is not used # TODO take from next-vote + vote_desc_items.append(DG_SUBMISSION_DESCRIPTION) call_script_items.append(dg_call_script[0]) - # receive non-DG vote items from omnibus contract + # 2. receive non-DG vote items from omnibus contract voting_items = interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).getVotingVoteItems() for desc, call_script in voting_items: @@ -71,7 +73,7 @@ def start_vote(tx_params: Dict[str, str], silent: bool = False): create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) ) - # TODO assert interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).isValidVoteScript(vote_id, ) + assert interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).isValidVoteScript(vote_id, DG_PROPOSAL_DESCRIPTION) return vote_id, tx diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index 636495edb..9e36fd953 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -39,7 +39,7 @@ EXPECTED_DG_PROPOSAL_ID = 6 EXPECTED_VOTE_EVENTS_COUNT = 10 EXPECTED_DG_EVENTS_COUNT = 17 -IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" +IPFS_DESCRIPTION_HASH = "bafkreic4xuaowfowt7faxnngnzynv7biuo7guv4s4jrngngjzzxyz3up2i" @pytest.fixture(scope="module") From 62d888621f39fb00cf638e3b8128e8a3d0937e25 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 25 Nov 2025 16:37:51 +0000 Subject: [PATCH 048/178] feat: more voting items --- interfaces/PSMVariant1Actions.json | 1 + interfaces/Susds.json | 1 + scripts/vote_2025_12_10.py | 29 +- tests/test_2025_12_10.py | 458 ++++++++++++++++++++++++++--- utils/test/easy_track_helpers.py | 8 +- 5 files changed, 445 insertions(+), 52 deletions(-) create mode 100644 interfaces/PSMVariant1Actions.json create mode 100644 interfaces/Susds.json diff --git a/interfaces/PSMVariant1Actions.json b/interfaces/PSMVariant1Actions.json new file mode 100644 index 000000000..1d5a50792 --- /dev/null +++ b/interfaces/PSMVariant1Actions.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_psm","type":"address"},{"internalType":"address","name":"_savingsToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"dai","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gem","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"psm","outputs":[{"internalType":"contract PSMVariant1Like","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"redeemAndSwap","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"savingsToken","outputs":[{"internalType":"contract IERC4626","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"swapAndDeposit","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"maxAmountIn","type":"uint256"}],"name":"withdrawAndSwap","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/interfaces/Susds.json b/interfaces/Susds.json new file mode 100644 index 000000000..d17278bde --- /dev/null +++ b/interfaces/Susds.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"usdsJoin_","type":"address"},{"internalType":"address","name":"vow_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"usr","type":"address"}],"name":"Deny","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"chi","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"diff","type":"uint256"}],"name":"Drip","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"what","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"data","type":"uint256"}],"name":"File","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint16","name":"referral","type":"uint16"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Referral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"usr","type":"address"}],"name":"Rely","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chi","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint16","name":"referral","type":"uint16"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"drip","outputs":[{"internalType":"uint256","name":"nChi","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint16","name":"referral","type":"uint16"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rho","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ssr","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"usds","outputs":[{"internalType":"contract UsdsLike","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdsJoin","outputs":[{"internalType":"contract UsdsJoinLike","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vow","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 9b2a93fd8..9ef039506 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -67,6 +67,14 @@ class TokenLimit(NamedTuple): SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +CURATED_MODULE_ID = 1 +CURATED_MODULE_TARGET_SHARE_BP = 10000 +CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 10000 +CURATED_MODULE_NEW_MODULE_FEE_BP = 350 +CURATED_MODULE_NEW_TREASURY_FEE_BP = 650 +CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + TRP_PERIOD_DURATION_MONTHS = 12 TRP_NEW_LIMIT = 15_000_000 * 10**18 TRP_NEW_SPENT_AMOUNT = 0 @@ -175,7 +183,22 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_items = [ agent_forward([ - # 1.1. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.1. change curated module + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + CURATED_MODULE_ID, + CURATED_MODULE_TARGET_SHARE_BP, + CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + CURATED_MODULE_NEW_MODULE_FEE_BP, + CURATED_MODULE_NEW_TREASURY_FEE_BP, + CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK, + CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), + agent_forward([ + # 1.2. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateStakingModule.encode_input( @@ -190,11 +213,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + # 1.3. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), ]), agent_forward([ - # 1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + # 1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months set_limit_parameters( limit=TRP_NEW_LIMIT, period_duration_months=TRP_PERIOD_DURATION_MONTHS, diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 28a37d60e..49a3ee596 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -82,6 +82,20 @@ class TokenLimit(NamedTuple): LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0xE1f6BaBb445F809B97e3505Ea91749461050F780" LIDO_LABS_TRUSTED_CALLER = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY = "0x68267f3D310E9f0FF53a37c141c90B738E1133c2" +REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" +WSTETH_TOKEN = "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" +PSM_VARIANT1_ACTIONS = "0xd0A61F2963622e992e6534bde4D52fd0a89F39E0" + +CURATED_MODULE_ID = 1 +CURATED_MODULE_TARGET_SHARE_BP = 10000 +CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 10000 +CURATED_MODULE_OLD_MODULE_FEE_BP = 500 +CURATED_MODULE_NEW_MODULE_FEE_BP = 350 +CURATED_MODULE_OLD_TREASURY_FEE_BP = 500 +CURATED_MODULE_NEW_TREASURY_FEE_BP = 650 +CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +CURATED_MODULE_NAME = "curated-onchain-v1" SDVT_MODULE_ID = 2 SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 @@ -113,12 +127,13 @@ class TokenLimit(NamedTuple): DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" ALLOWED_TOKENS_BEFORE = 3 ALLOWED_TOKENS_AFTER = 4 +ET_LIDO_LABS_STABLES_LIMIT = 15_000_000 EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 EXPECTED_VOTE_EVENTS_COUNT = 7 -EXPECTED_DG_EVENTS_COUNT = 3 +EXPECTED_DG_EVENTS_COUNT = 4 IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" @@ -311,6 +326,21 @@ def dual_governance_proposal_calls(): # Create all the dual governance calls that match the voting script dg_items = [ + agent_forward([ + # 1.1. change curated module + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + CURATED_MODULE_ID, + CURATED_MODULE_TARGET_SHARE_BP, + CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + CURATED_MODULE_NEW_MODULE_FEE_BP, + CURATED_MODULE_NEW_TREASURY_FEE_BP, + CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK, + CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), agent_forward([ ( staking_router.address, @@ -485,7 +515,69 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert id == amount_limits_after()[i].id assert op == amount_limits_after()[i].op.value assert val == amount_limits_after()[i].value - + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdt_limit_after.address), convert.to_uint(stranger.address), usdt_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdt_limit_after.address), convert.to_uint(stranger.address), usdt_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdc_limit_after.address), convert.to_uint(stranger.address), usdc_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdc_limit_after.address), convert.to_uint(stranger.address), usdc_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit + 1], + ) assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT @@ -556,17 +648,98 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # =========================== Scenario checks =========================== # ======================================================================= + # put a lot of tokens into Agent to check limits + prepare_agent_for_dai_payment(30_000_000 * 10**18) + prepare_agent_for_usdt_payment(30_000_000 * 10**6) + prepare_agent_for_usdc_payment(30_000_000 * 10**6) + prepare_agent_for_susds_payment(30_000_000 * 10**18) + prepare_agent_for_ldo_payment(10_000_000 * 10**18) + prepare_agent_for_steth_payment(2_000 * 10**18) + + # check ET limits - #et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_SANDBOX_STABLES_LIMIT * 10**18) - #et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_SANDBOX_STABLES_LIMIT * 10**6) - #et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_SANDBOX_STABLES_LIMIT * 10**18) - #et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_SANDBOX_STABLES_LIMIT * 10**6) + et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 1_000_000 * 10**18, "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030", "0x00caAeF11EC545B192f16313F53912E453c91458") + et_limit_test(stranger, interface.ERC20(LIDO), steth_limit_after.limit, 1_000 * 10**18, "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53", "0x200dA0b6a9905A377CF8D469664C65dB267009d1") + # check Finance limits - #finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18) - #finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6) - #finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18) - #finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6) + finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030", "0x00caAeF11EC545B192f16313F53912E453c91458", "0x97615f72c3428A393d65A84A3ea6BBD9ad6C0D74") + finance_limit_test(stranger, interface.ERC20(LIDO), steth_limit_after.limit, 18, "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53", "0x200dA0b6a9905A377CF8D469664C65dB267009d1", "0x49d1363016aA899bba09ae972a1BF200dDf8C55F") + + # sUSDS can be removed after being added to the allowed list + chain.snapshot() + allowed_tokens_registry.grantRole( + convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), + VOTING, + {"from": VOTING} + ) + assert allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_registry.removeToken( + SUSDS_TOKEN, + {"from": VOTING} + ) + assert not allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + with reverts("TOKEN_NOT_ALLOWED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(SUSDS_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # spending tokens not from the allowed list should fail + chain.snapshot() + with reverts("TOKEN_NOT_ALLOWED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(WSTETH_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # spending the allowed token not from the Finance CREATE_PAYMENTS_ROLE's list should fail + chain.snapshot() + allowed_tokens_registry.grantRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING, + {"from": VOTING} + ) + assert not allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + allowed_tokens_registry.addToken( + WSTETH_TOKEN, + {"from": VOTING} + ) + assert allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + with reverts("APP_AUTH_FAILED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(WSTETH_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # happy path + usds_wrap_happy_path(stranger) if EXPECTED_DG_PROPOSAL_ID is not None: @@ -577,7 +750,18 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # TODO add DG before proposal executed checks - # 1.1. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.1. curated changes + curated_module_before = staking_router.getStakingModule(CURATED_MODULE_ID) + assert curated_module_before['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP + assert curated_module_before['id'] == CURATED_MODULE_ID + assert curated_module_before['priorityExitShareThreshold'] == CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert curated_module_before['stakingModuleFee'] == CURATED_MODULE_OLD_MODULE_FEE_BP + assert curated_module_before['treasuryFee'] == CURATED_MODULE_OLD_TREASURY_FEE_BP + assert curated_module_before['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK + assert curated_module_before['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert curated_module_before['name'] == CURATED_MODULE_NAME + + # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP assert sdvt_module_before['id'] == SDVT_MODULE_ID @@ -620,6 +804,19 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g validate_staking_module_update_event( event=dg_events[0], + module_item=StakingModuleItem( + id=CURATED_MODULE_ID, + name=CURATED_MODULE_NAME, + address=None, + target_share=CURATED_MODULE_TARGET_SHARE_BP, + module_fee=CURATED_MODULE_NEW_MODULE_FEE_BP, + treasury_fee=CURATED_MODULE_NEW_TREASURY_FEE_BP, + priority_exit_share=CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + emitted_by=STAKING_ROUTER + ) + + validate_staking_module_update_event( + event=dg_events[1], module_item=StakingModuleItem( id=SDVT_MODULE_ID, name=SDVT_MODULE_NAME, @@ -632,13 +829,13 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g ) validate_set_spent_amount_event( - dg_events[1], + dg_events[2], new_spent_amount=0, emitted_by=ET_TRP_REGISTRY, ) validate_set_limit_parameter_event( - dg_events[2], + dg_events[3], limit=TRP_LIMIT_AFTER, period_duration_month=TRP_PERIOD_DURATION_MONTHS, period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, @@ -650,7 +847,32 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # TODO add DG after proposal executed checks - # 1.1. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.1. curated changes + curated_module_after = staking_router.getStakingModule(CURATED_MODULE_ID) + assert curated_module_after['stakingModuleFee'] == CURATED_MODULE_NEW_MODULE_FEE_BP + assert curated_module_after['treasuryFee'] == CURATED_MODULE_NEW_TREASURY_FEE_BP + assert curated_module_after['id'] == CURATED_MODULE_ID + assert curated_module_after['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP + assert curated_module_after['priorityExitShareThreshold'] == CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert curated_module_after['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK + assert curated_module_after['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert curated_module_after['name'] == CURATED_MODULE_NAME + # additional checks to make sure no other fields were changed + assert curated_module_after['id'] == curated_module_before['id'] + assert curated_module_after['stakingModuleAddress'] == curated_module_before['stakingModuleAddress'] + assert curated_module_after['stakeShareLimit'] == curated_module_before['stakeShareLimit'] + assert curated_module_after['status'] == curated_module_before['status'] + assert curated_module_after['name'] == curated_module_before['name'] + assert curated_module_after['lastDepositAt'] == curated_module_before['lastDepositAt'] + assert curated_module_after['lastDepositBlock'] == curated_module_before['lastDepositBlock'] + assert curated_module_after['exitedValidatorsCount'] == curated_module_before['exitedValidatorsCount'] + assert curated_module_after['maxDepositsPerBlock'] == curated_module_before['maxDepositsPerBlock'] + assert curated_module_after['minDepositBlockDistance'] == curated_module_before['minDepositBlockDistance'] + assert curated_module_after['priorityExitShareThreshold'] == curated_module_before['priorityExitShareThreshold'] + assert len(curated_module_after.items()) == len(curated_module_before.items()) + assert len(curated_module_after.items()) == 13 + + # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP assert sdvt_module_after['id'] == SDVT_MODULE_ID @@ -714,18 +936,23 @@ def trp_limit_test(stranger): ) # spend all step by step + recipients = [] + amounts = [] while to_spend > 0: - create_and_enact_payment_motion( - easy_track, - TRP_COMMITTEE, - TRP_TOP_UP_EVM_SCRIPT_FACTORY, - ldo_token, - [trp_committee_account], - [min(max_spend_at_once, to_spend)], - stranger, - ) + recipients.append(trp_committee_account) + amounts.append(min(max_spend_at_once, to_spend)) to_spend -= min(max_spend_at_once, to_spend) + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + recipients, + amounts, + stranger, + ) + # make sure there is nothing left so that you can't spend anymore with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): create_and_enact_payment_motion( @@ -740,10 +967,10 @@ def trp_limit_test(stranger): chain.revert() -def et_limit_test(stranger, token, max_spend_at_once, to_spend): +def et_limit_test(stranger, token, max_spend_at_once, to_spend, TRUSTED_CALLER, TOP_UP_ALLOWED_RECIPIENTS_FACTORY): easy_track = interface.EasyTrack(EASY_TRACK) - trusted_caller_account = accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True) + trusted_caller_account = accounts.at(TRUSTED_CALLER, force=True) chain.snapshot() @@ -751,8 +978,8 @@ def et_limit_test(stranger, token, max_spend_at_once, to_spend): with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): create_and_enact_payment_motion( easy_track, - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, token, [trusted_caller_account], [to_spend + 1], @@ -760,24 +987,29 @@ def et_limit_test(stranger, token, max_spend_at_once, to_spend): ) # spend all step by step + recipients = [] + amounts = [] while to_spend > 0: - create_and_enact_payment_motion( - easy_track, - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - token, - [trusted_caller_account], - [min(max_spend_at_once, to_spend)], - stranger, - ) + recipients.append(trusted_caller_account) + amounts.append(min(max_spend_at_once, to_spend)) to_spend -= min(max_spend_at_once, to_spend) + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + recipients, + amounts, + stranger, + ) + # make sure there is nothing left so that you can't spend anymore with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): create_and_enact_payment_motion( easy_track, - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, token, [trusted_caller_account], [1], @@ -787,16 +1019,16 @@ def et_limit_test(stranger, token, max_spend_at_once, to_spend): chain.revert() -def finance_limit_test(stranger, token, to_spend, decimals): +def finance_limit_test(stranger, token, to_spend, decimals, TRUSTED_CALLER, TOP_UP_ALLOWED_RECIPIENTS_FACTORY, ALLOWED_RECIPIENTS_REGISTRY): easy_track = interface.EasyTrack(EASY_TRACK) - trusted_caller_account = accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True) + trusted_caller_account = accounts.at(TRUSTED_CALLER, force=True) chain.snapshot() - # for Finance limit check - we first raise ET limits to 2 x finance_limit to be able to spend via Finance - interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).setLimitParameters( - (to_spend / (10**decimals) * 10**18) * 2, # 2 x finance_limit + # for Finance limit check - we first raise ET limits to 10 x finance_limit to be able to spend via Finance + interface.AllowedRecipientRegistry(ALLOWED_RECIPIENTS_REGISTRY).setLimitParameters( + (to_spend / (10**decimals) * 10**18) * 10, # 10 x finance_limit 3, # 3 months {"from": AGENT} ) @@ -805,8 +1037,8 @@ def finance_limit_test(stranger, token, to_spend, decimals): with reverts("APP_AUTH_FAILED"): create_and_enact_payment_motion( easy_track, - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, token, [trusted_caller_account], [to_spend + 1], @@ -816,12 +1048,142 @@ def finance_limit_test(stranger, token, to_spend, decimals): # spend the allowed balance create_and_enact_payment_motion( easy_track, - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, token, [trusted_caller_account], [to_spend], stranger, ) - chain.revert() \ No newline at end of file + chain.revert() + + +def prepare_agent_for_dai_payment(amount: int): + agent, dai = interface.Agent(AGENT), interface.Dai(DAI_TOKEN) + if dai.balanceOf(agent) < amount: + dai_ward_impersonated = accounts.at("0x9759A6Ac90977b93B58547b4A71c78317f391A28", force=True) + dai.mint(agent, amount, {"from": dai_ward_impersonated}) + + assert dai.balanceOf(agent) >= amount, f"Insufficient DAI balance" + + +def prepare_agent_for_usdc_payment(amount: int): + agent, usdc = interface.Agent(AGENT), interface.Usdc(USDC_TOKEN) + if usdc.balanceOf(agent) < amount: + usdc_minter = accounts.at("0x5B6122C109B78C6755486966148C1D70a50A47D7", force=True) + usdc_controller = accounts.at("0x79E0946e1C186E745f1352d7C21AB04700C99F71", force=True) + usdc_master_minter = interface.UsdcMasterMinter("0xE982615d461DD5cD06575BbeA87624fda4e3de17") + usdc_master_minter.incrementMinterAllowance(amount, {"from": usdc_controller}) + usdc.mint(agent, amount, {"from": usdc_minter}) + + assert usdc.balanceOf(agent) >= amount, "Insufficient USDC balance" + + +def prepare_agent_for_usdt_payment(amount: int): + agent, usdt = interface.Agent(AGENT), interface.Usdt(USDT_TOKEN) + if usdt.balanceOf(agent) < amount: + usdt_owner = accounts.at("0xC6CDE7C39eB2f0F0095F41570af89eFC2C1Ea828", force=True) + usdt.issue(amount, {"from": usdt_owner}) + usdt.transfer(agent, amount, {"from": usdt_owner}) + + assert usdt.balanceOf(agent) >= amount, "Insufficient USDT balance" + + +def prepare_agent_for_susds_payment(amount: int): + agent, susds = interface.Agent(AGENT), interface.ERC20(SUSDS_TOKEN) + if susds.balanceOf(agent) < amount: + susds_whale = accounts.at("0xBc65ad17c5C0a2A4D159fa5a503f4992c7B545FE", force=True) + susds.transfer(agent, amount, {"from": susds_whale}) + + assert susds.balanceOf(agent) >= amount, "Insufficient sUSDS balance" + + +def prepare_agent_for_ldo_payment(amount: int): + agent, ldo = interface.Agent(AGENT), interface.ERC20(LDO_TOKEN) + assert ldo.balanceOf(agent) >= amount, "Insufficient LDO balance 🫡" + + +def prepare_agent_for_steth_payment(amount: int): + STETH_TRANSFER_MAX_DELTA = 2 + + agent, steth = interface.Agent(AGENT), interface.Lido(LIDO) + eth_whale = accounts.at("0x00000000219ab540356cBB839Cbe05303d7705Fa", force=True) + if steth.balanceOf(agent) < amount: + steth.submit(ZERO_ADDRESS, {"from": eth_whale, "value": amount + 2 * STETH_TRANSFER_MAX_DELTA}) + steth.transfer(agent, amount + STETH_TRANSFER_MAX_DELTA, {"from": eth_whale}) + assert steth.balanceOf(agent) >= amount, "Insufficient stETH balance" + + +def usds_wrap_happy_path(stranger): + USDC_FOR_TRANSFER = 1000 + USDS_TOKEN = "0xdC035D45d973E3EC169d2276DDab16f1e407384F" + + easy_track = interface.EasyTrack(EASY_TRACK) + usdc = interface.Usdc(USDC_TOKEN) + psmVariant1Actions = interface.PSMVariant1Actions(PSM_VARIANT1_ACTIONS) + usds_token = interface.Usds(USDS_TOKEN) + susds_token = interface.Susds(SUSDS_TOKEN) + + eoa = accounts[0] + + chain.snapshot() + + initial_susds_agent_balance = susds_token.balanceOf(AGENT) + + # fund EOA with USDC from Treasury + interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).addRecipient( + eoa.address, + "EOA_test", + {"from": AGENT} + ) + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + usdc, + [eoa], + [USDC_FOR_TRANSFER * 10**6], + stranger, + ) + assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 + assert usds_token.balanceOf(eoa.address) == 0 + assert susds_token.balanceOf(eoa.address) == 0 + + # wrap USDC to sUSDS via PSM + usdc.approve(PSM_VARIANT1_ACTIONS, USDC_FOR_TRANSFER * 10**6, {"from": eoa}) + psmVariant1Actions.swapAndDeposit(eoa.address, USDC_FOR_TRANSFER * 10**6, USDC_FOR_TRANSFER * 10**18, {"from": eoa}) + assert usdc.balanceOf(eoa.address) == 0 + assert usds_token.balanceOf(eoa.address) == 0 + susds_balance = susds_token.balanceOf(eoa.address) + assert susds_balance <= USDC_FOR_TRANSFER * 10**18 + assert susds_balance >= USDC_FOR_TRANSFER * 10**18 * 0.9 + + # send sUSDS back to Treasury + susds_token.transfer(AGENT, susds_balance, {"from": eoa}) + assert susds_token.balanceOf(eoa.address) == 0 + assert susds_token.balanceOf(AGENT) == susds_balance + initial_susds_agent_balance + print("swapped", USDC_FOR_TRANSFER, "USDC to", susds_balance / 10**18, "sUSDS") + + # send sUSDS again to EOA via Easy Track payment from Treasury + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + susds_token, + [eoa], + [susds_balance], + stranger, + ) + assert susds_token.balanceOf(eoa.address) == susds_balance + assert susds_token.balanceOf(AGENT) == initial_susds_agent_balance + + # unwrap sUSDS to USDC + susds_token.approve(PSM_VARIANT1_ACTIONS, susds_balance, {"from": eoa}) + psmVariant1Actions.withdrawAndSwap(eoa.address, USDC_FOR_TRANSFER * 10**6, USDC_FOR_TRANSFER * 10**18, {"from": eoa}) + usdc_balance = usdc.balanceOf(eoa.address) + print("swapped", susds_balance / 10**18, "sUSDS to", usdc_balance / 10**6, "USDC") + assert susds_token.balanceOf(eoa.address) < 1.0 * 10**18 # dust + assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 + + chain.revert() diff --git a/utils/test/easy_track_helpers.py b/utils/test/easy_track_helpers.py index 1a8bb7aea..3a0158df6 100644 --- a/utils/test/easy_track_helpers.py +++ b/utils/test/easy_track_helpers.py @@ -77,9 +77,15 @@ def create_and_enact_payment_motion( create_and_enact_motion(easy_track, trusted_caller, factory, calldata, stranger) recievers_balance_after = [balance_of(reciever, token) for reciever in recievers] + recievers_total_amounts = {} for i in range(len(recievers)): + reciever_address = recievers[i].address + recievers_total_amounts[reciever_address] = recievers_total_amounts.get(reciever_address, 0) + transfer_amounts[i] + + for i in range(len(recievers)): + reciever_address = recievers[i].address assert almostEqWithDiff( - recievers_balance_after[i], recievers_balance_before[i] + transfer_amounts[i], STETH_ERROR_MARGIN_WEI + recievers_balance_after[i], recievers_balance_before[i] + recievers_total_amounts[reciever_address], STETH_ERROR_MARGIN_WEI ) agent_balance_after = balance_of(agent, token) From 404fce68ba0fd36360ef04c6c661b61694e1f336 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 25 Nov 2025 19:53:44 +0300 Subject: [PATCH 049/178] chore: fix voting items numeration --- scripts/upgrade_2025_12_10_mainnet_v3.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index cef0e83c8..49ecdc147 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -2,10 +2,18 @@ # Vote 2025_12_10 === 1. DG PROPOPSAL === -I. Lido V3 upgrade - stVaults +1. Lido V3 upgrade - stVaults === NON-DG ITEMS === -II. Lido V3 ET factories rights +2. Add AlterTiersInOperatorGrid factory to EasyTrack (permissions: operatorGrid, alterTiers); +3. Add RegisterGroupsInOperatorGrid factory to EasyTrack (permissions: operatorGrid, registerGroup + registerTiers); +4. Add RegisterTiersInOperatorGrid factory to EasyTrack (permissions: operatorGrid, registerTiers); +5. Add UpdateGroupsShareLimitInOperatorGrid factory to EasyTrack (permissions: operatorGrid, updateGroupShareLimit); +6. Add SetJailStatusInOperatorGrid factory to EasyTrack (permissions: vaultsAdapter, setVaultJailStatus); +7. Add UpdateVaultsFeesInOperatorGrid factory to EasyTrack (permissions: vaultsAdapter, updateVaultFees); +8. Add ForceValidatorExitsInVaultHub factory to EasyTrack (permissions: vaultsAdapter, forceValidatorExit); +9. Add SetLiabilitySharesTargetInVaultHub factory to EasyTrack (permissions: vaultsAdapter, setLiabilitySharesTarget); +10. Add SocializeBadDebtInVaultHub factory to EasyTrack (permissions: vaultsAdapter, socializeBadDebt). # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ @@ -28,7 +36,7 @@ # TODO IPFS_DESCRIPTION = "omni dec 2025" DG_PROPOSAL_DESCRIPTION = "TODO DG proposal description" -DG_SUBMISSION_DESCRIPTION = "TODO DG submission description" +DG_SUBMISSION_DESCRIPTION = "1. TODO DG submission description" # ================================ Main ====================================== From 49698da8edb797162093b8c6ffcd8b15a8ef2f99 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 25 Nov 2025 18:14:20 +0000 Subject: [PATCH 050/178] fix: cm fees --- configs/config_mainnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 5f3a64b42..860b0006d 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -141,8 +141,8 @@ CURATED_STAKING_MODULE_STUCK_PENALTY_DELAY = 0 CURATED_STAKING_MODULE_TARGET_SHARE_BP = 10000 -CURATED_STAKING_MODULE_MODULE_FEE_BP = 500 -CURATED_STAKING_MODULE_TREASURY_FEE_BP = 500 +CURATED_STAKING_MODULE_MODULE_FEE_BP = 350 +CURATED_STAKING_MODULE_TREASURY_FEE_BP = 650 CURATED_STAKING_MODULE_ID = 1 CURATED_STAKING_MODULE_NAME = "curated-onchain-v1" CURATED_STAKING_MODULE_TYPE = ( From 5adb36a306a3adbff77d0c75f5caca3591b9b075 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 25 Nov 2025 22:32:50 +0300 Subject: [PATCH 051/178] tests: fix skipped reggression tests --- tests/regression/test_pause_resume.py | 7 +---- tests/regression/test_sanity_checks.py | 29 +++++++++++++++---- .../test_staking_module_happy_path.py | 26 +++++++++-------- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/tests/regression/test_pause_resume.py b/tests/regression/test_pause_resume.py index 94156240b..46d29f424 100644 --- a/tests/regression/test_pause_resume.py +++ b/tests/regression/test_pause_resume.py @@ -135,11 +135,6 @@ def test_revert_second_stop_resume(self): with brownie.reverts("CONTRACT_IS_ACTIVE"): contracts.lido.resume({"from": contracts.agent}) - @pytest.mark.skip( - reason="Second call of pause/resume staking is not reverted right now." - "It maybe should be fixed in the future to be consistent, " - "there's not a real problem with it." - ) def test_revert_second_pause_resume_staking(self): contracts.lido.pauseStaking({"from": contracts.agent}) @@ -148,7 +143,7 @@ def test_revert_second_pause_resume_staking(self): contracts.lido.resumeStaking({"from": contracts.agent}) - with brownie.reverts(""): + with brownie.reverts("ALREADY_RESUMED"): contracts.lido.resumeStaking({"from": contracts.agent}) def test_revert_second_stop_staking_module(self, helpers, stranger): diff --git a/tests/regression/test_sanity_checks.py b/tests/regression/test_sanity_checks.py index dbd2bd44f..719d74cdc 100644 --- a/tests/regression/test_sanity_checks.py +++ b/tests/regression/test_sanity_checks.py @@ -241,20 +241,39 @@ def test_accounting_oracle_too_much_extra_data(extra_data_service): ) -@pytest.mark.skip("ganache throws 'RPCRequestError: Invalid string length' on such long extra data") def test_accounting_oracle_too_node_ops_per_extra_data_item(extra_data_service): - item_count = MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM * 10 - extra_data = extra_data_service.collect({(1, i): i for i in range(item_count)}, {}, 1, item_count) + node_ops_count = MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM + 1 + + # Collect real operators with their current exited validators count + operators = {} + nor_module_id = 1 + nor_operators_count = contracts.node_operators_registry.getNodeOperatorsCount() + i = 0 + + for no_id in range(nor_operators_count): + (active, _, _, _, total_exited_validators_count, _, total_deposited_validators_count) = contracts.node_operators_registry.getNodeOperator(no_id, True) + + if active and total_exited_validators_count != total_deposited_validators_count: + operators[(nor_module_id, no_id)] = total_exited_validators_count + 1 + i += 1 + + if i == node_ops_count: + break + + # Create extra data with too many node operators in a single item + # by setting max_no_in_payload_count to a large value + extra_data = extra_data_service.collect(operators, 1, node_ops_count) + with reverts( encode_error( "TooManyNodeOpsPerExtraDataItem(uint256,uint256)", - [MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, item_count], + [0, node_ops_count], # itemIndex=0, nodeOpsCount=25 ) ): oracle_report( extraDataFormat=1, extraDataHashList=extra_data.extra_data_hash_list, - extraDataItemsCount=1, + extraDataItemsCount=extra_data.items_count, extraDataList=extra_data.extra_data_list, ) diff --git a/tests/regression/test_staking_module_happy_path.py b/tests/regression/test_staking_module_happy_path.py index 375bf74a9..9807e4885 100644 --- a/tests/regression/test_staking_module_happy_path.py +++ b/tests/regression/test_staking_module_happy_path.py @@ -198,10 +198,15 @@ def module_happy_path(staking_module, extra_data_service, impersonated_agent, st # - Check NOs stats # - Check Report events - # Prepare extra data + # Get current exited validators count for operators + no1_exited_before = staking_module.getNodeOperatorSummary(no1_id)["totalExitedValidators"] + no2_exited_before = staking_module.getNodeOperatorSummary(no2_id)["totalExitedValidators"] + no3_exited_before = staking_module.getNodeOperatorSummary(no3_id)["totalExitedValidators"] + + # Prepare extra data - set 5 more exited validators for each operator vals_exited_non_zero = { - node_operator_gindex(staking_module.module_id, no1_id): 5, - node_operator_gindex(staking_module.module_id, no2_id): 5, + node_operator_gindex(staking_module.module_id, no1_id): no1_exited_before + 5, + node_operator_gindex(staking_module.module_id, no2_id): no2_exited_before + 5, } extra_data = extra_data_service.collect(vals_exited_non_zero, 10, 10) @@ -247,20 +252,20 @@ def module_happy_path(staking_module, extra_data_service, impersonated_agent, st assert no2_balance_shares_after - no2_balance_shares_before == no2_rewards_after_second_report assert no3_balance_shares_after - no3_balance_shares_before == no3_rewards_after_second_report - # NO stats - assert no1_summary["totalExitedValidators"] == 5 - assert no2_summary["totalExitedValidators"] == 5 - assert no3_summary["totalExitedValidators"] == 0 + # NO stats - check that exited validators increased by 5 for no1 and no2, and stayed the same for no3 + assert no1_summary["totalExitedValidators"] == no1_exited_before + 5 + assert no2_summary["totalExitedValidators"] == no2_exited_before + 5 + assert no3_summary["totalExitedValidators"] == no3_exited_before # Events exited_signing_keys_count_events = parse_exited_signing_keys_count_changed_logs( filter_transfer_logs(extra_report_tx_list[0].logs, web3.keccak(text="ExitedSigningKeysCountChanged(uint256,uint256)")) ) assert exited_signing_keys_count_events[0]["nodeOperatorId"] == no1_id - assert exited_signing_keys_count_events[0]["exitedValidatorsCount"][0] == 5 + assert exited_signing_keys_count_events[0]["exitedValidatorsCount"][0] == no1_exited_before + 5 assert exited_signing_keys_count_events[1]["nodeOperatorId"] == no2_id - assert exited_signing_keys_count_events[1]["exitedValidatorsCount"][0] == 5 + assert exited_signing_keys_count_events[1]["exitedValidatorsCount"][0] == no2_exited_before + 5 # Deposit keys deposit_and_check_keys(staking_module, no1_id, no2_id, no3_id, 50, impersonated_agent) @@ -350,9 +355,6 @@ def module_happy_path(staking_module, extra_data_service, impersonated_agent, st assert no3_deposited_keys_before != no3_deposited_keys_after -@pytest.mark.skip( - "TODO: fix the test assumptions about the state of the chain (no exited validators, depositable ETH amount)" -) def test_node_operator_registry(impersonated_agent, stranger, helpers): nor = contracts.node_operators_registry nor.module_id = 1 From ed75ff7092ad249cdf0097927b967c362f20773e Mon Sep 17 00:00:00 2001 From: skhomuti Date: Wed, 26 Nov 2025 12:12:53 +0500 Subject: [PATCH 052/178] fix incorrect treasury fee calculations for curated --- tests/regression/test_pause_resume.py | 29 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/tests/regression/test_pause_resume.py b/tests/regression/test_pause_resume.py index 36b21b978..47aa9195f 100644 --- a/tests/regression/test_pause_resume.py +++ b/tests/regression/test_pause_resume.py @@ -7,6 +7,7 @@ from utils.config import contracts from utils.evm_script import encode_error from utils.import_current_votes import is_there_any_vote_scripts, start_and_execute_votes +from utils.staking_module import calc_module_reward_shares from utils.test.oracle_report_helpers import oracle_report, prepare_exit_bus_report from utils.test.helpers import almostEqEth, almostEqWithDiff @@ -276,24 +277,30 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): (report_tx, _) = oracle_report() print(report_tx.events["Transfer"]) - module_index = 0 + curated_index = 0 simple_dvt_index = 1 csm_index = 2 - if report_tx.events["Transfer"][module_index]["to"] == burner.address: - module_index += 1 + if report_tx.events["Transfer"][curated_index]["to"] == burner.address: + curated_index += 1 simple_dvt_index += 1 csm_index += 1 - agent_index = module_index + 3 - assert report_tx.events["Transfer"][module_index]["to"] == module_address - assert report_tx.events["Transfer"][module_index]["from"] == ZERO_ADDRESS + agent_index = curated_index + 3 + assert report_tx.events["Transfer"][curated_index]["to"] == module_address + assert report_tx.events["Transfer"][curated_index]["from"] == ZERO_ADDRESS assert report_tx.events["Transfer"][agent_index]["to"] == contracts.agent assert report_tx.events["Transfer"][agent_index]["from"] == ZERO_ADDRESS - # the staking modules ids starts from 1, so SDVT has id = 2 + curated_stats = contracts.staking_router.getStakingModule(1) + curated_treasury_fee = ( + report_tx.events["Transfer"][curated_index]["value"] + * 100_00 + // curated_stats["stakingModuleFee"] + * curated_stats["treasuryFee"] + // 100_00 + ) simple_dvt_stats = contracts.staking_router.getStakingModule(2) - # simple_dvt_treasury_fee = sdvt_share / share_pct * treasury_pct simple_dvt_treasury_fee = ( report_tx.events["Transfer"][simple_dvt_index]["value"] * 100_00 @@ -310,11 +317,13 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): // 100_00 ) assert almostEqWithDiff( - report_tx.events["Transfer"][module_index]["value"] + simple_dvt_treasury_fee + csm_treasury_fee, + curated_treasury_fee + simple_dvt_treasury_fee + csm_treasury_fee, report_tx.events["Transfer"][agent_index]["value"], 100, ) - assert report_tx.events["Transfer"][module_index]["value"] > 0 + assert report_tx.events["Transfer"][curated_index]["value"] > 0 + assert report_tx.events["Transfer"][simple_dvt_index]["value"] > 0 + assert report_tx.events["Transfer"][csm_index]["value"] > 0 def test_stopped_staking_module_cant_stake(stranger): From f7ee713217e6b7ab9a82d80b15bdf6fd73d13a6f Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 26 Nov 2025 07:21:42 +0000 Subject: [PATCH 053/178] fix: descs --- scripts/vote_2025_12_10.py | 44 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 9ef039506..36610c8cd 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -17,6 +17,7 @@ """ from typing import Dict, List, Tuple, NamedTuple +from brownie import interface, ZERO_ADDRESS, convert, web3 from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op from utils.finance import make_matic_payout @@ -27,13 +28,13 @@ from utils.dual_governance import submit_proposals from utils.agent import agent_forward from utils.permissions import encode_permission_revoke, encode_permission_grant_p -from brownie import interface, ZERO_ADDRESS, convert, web3 from utils.allowed_recipients_registry import ( unsafe_set_spent_amount, set_limit_parameters, ) +# ============================== Types =================================== class TokenLimit(NamedTuple): address: str limit: int @@ -46,10 +47,15 @@ class TokenLimit(NamedTuple): FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" + + +# ============================== Roles =================================== CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" -VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" + +# ============================== Tokens =================================== SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" @@ -59,14 +65,6 @@ class TokenLimit(NamedTuple): # ============================== Constants =================================== -SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 -SDVT_MODULE_MODULE_FEE_BP = 800 -SDVT_MODULE_TREASURY_FEE_BP = 200 -SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 -SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 - CURATED_MODULE_ID = 1 CURATED_MODULE_TARGET_SHARE_BP = 10000 CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 10000 @@ -75,6 +73,14 @@ class TokenLimit(NamedTuple): CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +SDVT_MODULE_ID = 2 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 +SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_MODULE_FEE_BP = 800 +SDVT_MODULE_TREASURY_FEE_BP = 200 +SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + TRP_PERIOD_DURATION_MONTHS = 12 TRP_NEW_LIMIT = 15_000_000 * 10**18 TRP_NEW_SPENT_AMOUNT = 0 @@ -183,7 +189,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_items = [ agent_forward([ - # 1.1. change curated module + # 1.1. Change Curated Module (MODULE_ID = 1) module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateStakingModule.encode_input( @@ -198,7 +204,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.2. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateStakingModule.encode_input( @@ -227,12 +233,12 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ] dg_call_script = submit_proposals([ - (dg_items, "Upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit") + (dg_items, "Upgrade Lido Protocol to change Curated Module fees, raise SDVT stake share limit and reset Easy Track TRP limit") ]) vote_desc_items, call_script_items = zip( ( - "1. Submit a Dual Governance proposal to upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit", + "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit and reset Easy Track TRP limit", dg_call_script[0] ), ( @@ -244,7 +250,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "1. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting", + "3. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( allowed_tokens_registry.address, allowed_tokens_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), @@ -253,11 +259,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "4343. TODO", + "4. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca", (allowed_tokens_registry.address, allowed_tokens_registry.addToken.encode_input(SUSDS_TOKEN)) ), ( - "3. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting", + "5. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( allowed_tokens_registry.address, allowed_tokens_registry.revokeRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), @@ -266,7 +272,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ) ), ( - "3. TODO", + "6. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86", encode_permission_revoke( target_app=FINANCE, permission_name=CREATE_PAYMENTS_ROLE, @@ -274,7 +280,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "4. TODO", + "7. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS", encode_permission_grant_p( target_app=FINANCE, permission_name=CREATE_PAYMENTS_ROLE, From d48bb79ee28c948a832d5626c214a4facc01c3e5 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 26 Nov 2025 07:31:27 +0000 Subject: [PATCH 054/178] fix: descs --- scripts/vote_2025_12_10.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 36610c8cd..fa7cc5465 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -2,17 +2,30 @@ # Vote 2025_12_10 === 1. DG PROPOPSAL === -I. raise SDVT stake share limit -1.1. raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +I. Change Curated Module fees +1.1. Change Curated Module (MODULE_ID = 1) module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 -II. Reset Easy Track TRP limit -1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO -1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months +II. Raise SDVT stake share limit +1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + +III. Reset Easy Track TRP limit +# 1.3. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO +# 1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months === NON-DG ITEMS === -III. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig +IV. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 +V. Add sUSDS to stablecoins Allowed Tokens Registry +3. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e +4. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca +5. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e + +VI. Add sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance +6. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 +7. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS + + # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ From 4371ca051969d45f2e7692d702d27b0155900181 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 26 Nov 2025 08:06:19 +0000 Subject: [PATCH 055/178] fix: descs --- scripts/vote_2025_12_10.py | 14 +- tests/test_2025_12_10.py | 303 +++++++++++++++++++------------------ 2 files changed, 161 insertions(+), 156 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index fa7cc5465..d57a08273 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -59,7 +59,7 @@ class TokenLimit(NamedTuple): LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" -ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" +STABLECOINS_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" @@ -74,7 +74,7 @@ class TokenLimit(NamedTuple): USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" -LIDO = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" +STETH_TOKEN = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" # ============================== Constants =================================== @@ -103,7 +103,7 @@ class TokenLimit(NamedTuple): def amount_limits() -> List[Param]: ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) - steth_limit = TokenLimit(LIDO, 1_000 * (10**18)) + steth_limit = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) dai_limit = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) usdc_limit = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) usdt_limit = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) @@ -198,7 +198,7 @@ def amount_limits() -> List[Param]: def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: staking_router = interface.StakingRouter(STAKING_ROUTER) - allowed_tokens_registry = interface.AllowedTokensRegistry(ALLOWED_TOKENS_REGISTRY) + stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) dg_items = [ agent_forward([ @@ -265,7 +265,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ( "3. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( - allowed_tokens_registry.address, allowed_tokens_registry.grantRole.encode_input( + stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING, ) @@ -273,12 +273,12 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ( "4. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca", - (allowed_tokens_registry.address, allowed_tokens_registry.addToken.encode_input(SUSDS_TOKEN)) + (stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.addToken.encode_input(SUSDS_TOKEN)) ), ( "5. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( - allowed_tokens_registry.address, allowed_tokens_registry.revokeRole.encode_input( + stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.revokeRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING, ) diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 49a3ee596..f86f8f4ef 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -1,28 +1,29 @@ -from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 -from brownie.network.transaction import TransactionReceipt import pytest from typing import List, NamedTuple +from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 +from brownie.network.transaction import TransactionReceipt from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op -from utils.test.tx_tracing_helpers import ( - group_voting_events_from_receipt, - group_dg_events_from_receipt, - count_vote_items_by_events, - display_voting_events, - display_dg_events -) from utils.test.easy_track_helpers import create_and_enact_payment_motion from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id +from utils.agent import agent_forward from utils.ipfs import get_lido_vote_cid_from_str from utils.dual_governance import PROPOSAL_STATUS +from utils.test.event_validators.allowed_tokens_registry import validate_add_token_event from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event +from utils.test.tx_tracing_helpers import ( + group_voting_events_from_receipt, + group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events +) from utils.allowed_recipients_registry import ( unsafe_set_spent_amount, set_limit_parameters, ) -from utils.agent import agent_forward from utils.test.event_validators.payout import ( validate_token_payout_event, Payout, @@ -38,25 +39,18 @@ validate_permission_grantp_event, validate_permission_revoke_event, ) -from utils.test.event_validators.allowed_tokens_registry import validate_add_token_event + class TokenLimit(NamedTuple): address: str limit: int -# ============================================================================ # ============================== Import vote ================================= -# ============================================================================ from scripts.vote_2025_12_10 import start_vote, get_vote_items -# ============================================================================ -# ============================== Constants =================================== -# ============================================================================ -# TODO list all contract addresses used in tests - do not use imports from config! -# NOTE: these addresses might have a different value on other chains - +# ============================== Addresses =================================== VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" @@ -64,28 +58,41 @@ class TokenLimit(NamedTuple): DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" -DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" DEPOSIT_SECURITY_MODULE = "0xffa96d84def2ea035c7ab153d8b991128e3d72fd" -LIDO = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" -SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" EASY_TRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" -TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" -TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" -LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" -CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" ACL = "0x9895f0f17cc1d1891b6f18ee0b483b6f221b37bb" -ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" -ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" + +TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" +TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" + +STABLECOINS_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0xE1f6BaBb445F809B97e3505Ea91749461050F780" LIDO_LABS_TRUSTED_CALLER = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY = "0x68267f3D310E9f0FF53a37c141c90B738E1133c2" -REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" -WSTETH_TOKEN = "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" + +LEGO_LDO_TRUSTED_CALLER = "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030" +LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x00caAeF11EC545B192f16313F53912E453c91458" +LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY = "0x97615f72c3428A393d65A84A3ea6BBD9ad6C0D74" + +GAS_SUPPLY_STETH_TRUSTED_CALLER = "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53" +GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x200dA0b6a9905A377CF8D469664C65dB267009d1" +GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x49d1363016aA899bba09ae972a1BF200dDf8C55F" + +LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" +SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" +DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" PSM_VARIANT1_ACTIONS = "0xd0A61F2963622e992e6534bde4D52fd0a89F39E0" + +# ============================== Roles =================================== +CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" +ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" +REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" + + +# ============================== Constants =================================== CURATED_MODULE_ID = 1 CURATED_MODULE_TARGET_SHARE_BP = 10000 CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 10000 @@ -107,7 +114,6 @@ class TokenLimit(NamedTuple): SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 SDVT_MODULE_NAME = "SimpleDVT" -MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 MATIC_IN_TREASURY_AFTER = 165_781_175_837_137_177 MATIC_IN_LIDO_LABS_BEFORE = 0 @@ -121,15 +127,23 @@ class TokenLimit(NamedTuple): TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC TRP_PERIOD_DURATION_MONTHS = 12 +ALLOWED_TOKENS_BEFORE = 3 +ALLOWED_TOKENS_AFTER = 4 +ET_LIDO_LABS_STABLES_LIMIT = 15_000_000 + + +# ============================== Tokens =================================== +MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" +LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" +STETH_TOKEN = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" +WSTETH_TOKEN = "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" -ALLOWED_TOKENS_BEFORE = 3 -ALLOWED_TOKENS_AFTER = 4 -ET_LIDO_LABS_STABLES_LIMIT = 15_000_000 +# ============================== Voting =================================== EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 EXPECTED_VOTE_EVENTS_COUNT = 7 @@ -137,11 +151,12 @@ class TokenLimit(NamedTuple): IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" +# ============================== Finance Limits =================================== AMOUNT_LIMITS_LEN_BEFORE = 19 def amount_limits_before() -> List[Param]: ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) - steth_limit = TokenLimit(LIDO, 1_000 * (10**18)) + steth_limit = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) dai_limit = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) usdc_limit = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) usdt_limit = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) @@ -218,11 +233,10 @@ def amount_limits_before() -> List[Param]: return limits - AMOUNT_LIMITS_LEN_AFTER = 22 ldo_limit_after = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) eth_limit_after = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) -steth_limit_after = TokenLimit(LIDO, 1_000 * (10**18)) +steth_limit_after = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) dai_limit_after = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) usdc_limit_after = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) usdt_limit_after = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) @@ -324,10 +338,8 @@ def dual_governance_proposal_calls(): staking_router = interface.StakingRouter(STAKING_ROUTER) - # Create all the dual governance calls that match the voting script dg_items = [ agent_forward([ - # 1.1. change curated module ( staking_router.address, staking_router.updateStakingModule.encode_input( @@ -366,8 +378,7 @@ def dual_governance_proposal_calls(): ), ]), ] - - + # Convert each dg_item to the expected format proposal_calls = [] for dg_item in dg_items: @@ -394,7 +405,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g staking_router = interface.StakingRouter(STAKING_ROUTER) et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) acl = interface.ACL(ACL) - allowed_tokens_registry = interface.AllowedTokensRegistry(ALLOWED_TOKENS_REGISTRY) + stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) # ========================================================================= @@ -422,7 +433,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # ========================= Before voting checks ======================== # ======================================================================= - # TODO add before voting checks + + # Item 1 is DG - skipped here # Item 2 matic_treasury_balance_before = matic_token.balanceOf(agent.address) @@ -431,14 +443,14 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE # Items 3,5 - assert not allowed_tokens_registry.hasRole( + assert not stablecoins_allowed_tokens_registry.hasRole( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING ) # Item 4 - assert not allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - allowed_tokens_before = allowed_tokens_registry.getAllowedTokens() + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() assert len(allowed_tokens_before) == ALLOWED_TOKENS_BEFORE assert allowed_tokens_before[0] == DAI_TOKEN assert allowed_tokens_before[1] == USDT_TOKEN @@ -472,27 +484,29 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # ========================= After voting checks ========================= # ======================================================================= - # TODO add after voting tests + + # Item 1 is DG - skipped here # Item 2 matic_treasury_balance_after = matic_token.balanceOf(agent.address) assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER matic_labs_balance_after = matic_token.balanceOf(LOL_MS) assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER + # make sure LOL can actually spend the received MATIC matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 # Items 3,5 - assert not allowed_tokens_registry.hasRole( + assert not stablecoins_allowed_tokens_registry.hasRole( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING ) # Item 4 - assert allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - allowed_tokens_before = allowed_tokens_registry.getAllowedTokens() + assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() assert len(allowed_tokens_before) == ALLOWED_TOKENS_AFTER assert allowed_tokens_before[0] == DAI_TOKEN assert allowed_tokens_before[1] == USDT_TOKEN @@ -516,6 +530,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert op == amount_limits_after()[i].op.value assert val == amount_limits_after()[i].value + # check Finance create payment permissions with limits for all allowed tokens assert acl.hasPermission["address,address,bytes32,uint[]"]( ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit], @@ -584,17 +599,17 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g if EXPECTED_DG_PROPOSAL_ID is not None: assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - # Validate DG Proposal Submit event + # validate DG Proposal Submit event validate_dual_governance_submit_event( vote_events[0], proposal_id=EXPECTED_DG_PROPOSAL_ID, proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="Upgrade Lido Protocol to V3, raise SDVT stake share limit and reset Easy Track TRP limit", + metadata="Upgrade Lido Protocol to change Curated Module fees, raise SDVT stake share limit and reset Easy Track TRP limit", proposal_calls=dual_governance_proposal_calls, ) - # TODO validate all other voting events + # validate all other voting events validate_token_payout_event( event=vote_events[1], p=Payout( @@ -610,19 +625,19 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), grant_to=VOTING, sender=VOTING, - emitted_by=ALLOWED_TOKENS_REGISTRY, + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, ) validate_add_token_event( event=vote_events[3], token=SUSDS_TOKEN, - emitted_by=ALLOWED_TOKENS_REGISTRY + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY ) validate_revoke_role_event( events=vote_events[4], role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), revoke_from=VOTING, sender=VOTING, - emitted_by=ALLOWED_TOKENS_REGISTRY, + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, ) validate_permission_revoke_event( event=vote_events[5], @@ -645,10 +660,10 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g ) # ======================================================================= - # =========================== Scenario checks =========================== + # =========================== Scenario tests ============================ # ======================================================================= - # put a lot of tokens into Agent to check limits + # put a lot of tokens into Agent to check Finance/ET limits prepare_agent_for_dai_payment(30_000_000 * 10**18) prepare_agent_for_usdt_payment(30_000_000 * 10**6) prepare_agent_for_usdc_payment(30_000_000 * 10**6) @@ -656,37 +671,35 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g prepare_agent_for_ldo_payment(10_000_000 * 10**18) prepare_agent_for_steth_payment(2_000 * 10**18) - - # check ET limits + # check ET limits via Easy Track motion et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 1_000_000 * 10**18, "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030", "0x00caAeF11EC545B192f16313F53912E453c91458") - et_limit_test(stranger, interface.ERC20(LIDO), steth_limit_after.limit, 1_000 * 10**18, "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53", "0x200dA0b6a9905A377CF8D469664C65dB267009d1") - + et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 1_000_000 * 10**18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 1_000 * 10**18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - # check Finance limits + # check Finance limits via Easy Track motion finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030", "0x00caAeF11EC545B192f16313F53912E453c91458", "0x97615f72c3428A393d65A84A3ea6BBD9ad6C0D74") - finance_limit_test(stranger, interface.ERC20(LIDO), steth_limit_after.limit, 18, "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53", "0x200dA0b6a9905A377CF8D469664C65dB267009d1", "0x49d1363016aA899bba09ae972a1BF200dDf8C55F") + finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY) # sUSDS can be removed after being added to the allowed list chain.snapshot() - allowed_tokens_registry.grantRole( + stablecoins_allowed_tokens_registry.grantRole( convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), VOTING, {"from": VOTING} ) - assert allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - allowed_tokens_registry.removeToken( + assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + stablecoins_allowed_tokens_registry.removeToken( SUSDS_TOKEN, {"from": VOTING} ) - assert not allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) with reverts("TOKEN_NOT_ALLOWED"): create_and_enact_payment_motion( interface.EasyTrack(EASY_TRACK), @@ -715,17 +728,17 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # spending the allowed token not from the Finance CREATE_PAYMENTS_ROLE's list should fail chain.snapshot() - allowed_tokens_registry.grantRole( + stablecoins_allowed_tokens_registry.grantRole( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING, {"from": VOTING} ) - assert not allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) - allowed_tokens_registry.addToken( + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + stablecoins_allowed_tokens_registry.addToken( WSTETH_TOKEN, {"from": VOTING} ) - assert allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + assert stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) with reverts("APP_AUTH_FAILED"): create_and_enact_payment_motion( interface.EasyTrack(EASY_TRACK), @@ -748,9 +761,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # ================== DG before proposal executed checks =================== # ========================================================================= - # TODO add DG before proposal executed checks - # 1.1. curated changes + # Item 1.1 curated_module_before = staking_router.getStakingModule(CURATED_MODULE_ID) assert curated_module_before['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP assert curated_module_before['id'] == CURATED_MODULE_ID @@ -761,7 +773,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert curated_module_before['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE assert curated_module_before['name'] == CURATED_MODULE_NAME - # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # Item 1.2 sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP assert sdvt_module_before['id'] == SDVT_MODULE_ID @@ -772,8 +784,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE assert sdvt_module_before['name'] == SDVT_MODULE_NAME - # 1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - # 1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + # Items 1.3,1.4 trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() assert trp_limit_before == TRP_LIMIT_BEFORE @@ -800,8 +811,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT - # TODO validate all DG events - + # validate all DG events validate_staking_module_update_event( event=dg_events[0], module_item=StakingModuleItem( @@ -814,7 +824,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g priority_exit_share=CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP), emitted_by=STAKING_ROUTER ) - validate_staking_module_update_event( event=dg_events[1], module_item=StakingModuleItem( @@ -827,13 +836,11 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), emitted_by=STAKING_ROUTER ) - validate_set_spent_amount_event( dg_events[2], new_spent_amount=0, emitted_by=ET_TRP_REGISTRY, ) - validate_set_limit_parameter_event( dg_events[3], limit=TRP_LIMIT_AFTER, @@ -845,9 +852,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================================================================= # ==================== After DG proposal executed checks ================== # ========================================================================= - # TODO add DG after proposal executed checks - # 1.1. curated changes + # Item 1.1 curated_module_after = staking_router.getStakingModule(CURATED_MODULE_ID) assert curated_module_after['stakingModuleFee'] == CURATED_MODULE_NEW_MODULE_FEE_BP assert curated_module_after['treasuryFee'] == CURATED_MODULE_NEW_TREASURY_FEE_BP @@ -872,7 +878,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert len(curated_module_after.items()) == len(curated_module_before.items()) assert len(curated_module_after.items()) == 13 - # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # Item 1.2 sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP assert sdvt_module_after['id'] == SDVT_MODULE_ID @@ -898,8 +904,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) assert len(sdvt_module_after.items()) == 13 - # 1.2. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - # 1.3. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + # Items 1.3,1.4 trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() assert trp_limit_after == TRP_LIMIT_AFTER @@ -909,7 +914,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - # additional test for TRP ET factory behavior after the vote + # scenraio test for TRP ET factory behavior after the vote trp_limit_test(stranger) @@ -935,7 +940,7 @@ def trp_limit_test(stranger): stranger, ) - # spend all step by step + # spend all in several transfers recipients = [] amounts = [] while to_spend > 0: @@ -986,7 +991,7 @@ def et_limit_test(stranger, token, max_spend_at_once, to_spend, TRUSTED_CALLER, stranger, ) - # spend all step by step + # spend all in several transfers recipients = [] amounts = [] while to_spend > 0: @@ -1059,62 +1064,6 @@ def finance_limit_test(stranger, token, to_spend, decimals, TRUSTED_CALLER, TOP_ chain.revert() -def prepare_agent_for_dai_payment(amount: int): - agent, dai = interface.Agent(AGENT), interface.Dai(DAI_TOKEN) - if dai.balanceOf(agent) < amount: - dai_ward_impersonated = accounts.at("0x9759A6Ac90977b93B58547b4A71c78317f391A28", force=True) - dai.mint(agent, amount, {"from": dai_ward_impersonated}) - - assert dai.balanceOf(agent) >= amount, f"Insufficient DAI balance" - - -def prepare_agent_for_usdc_payment(amount: int): - agent, usdc = interface.Agent(AGENT), interface.Usdc(USDC_TOKEN) - if usdc.balanceOf(agent) < amount: - usdc_minter = accounts.at("0x5B6122C109B78C6755486966148C1D70a50A47D7", force=True) - usdc_controller = accounts.at("0x79E0946e1C186E745f1352d7C21AB04700C99F71", force=True) - usdc_master_minter = interface.UsdcMasterMinter("0xE982615d461DD5cD06575BbeA87624fda4e3de17") - usdc_master_minter.incrementMinterAllowance(amount, {"from": usdc_controller}) - usdc.mint(agent, amount, {"from": usdc_minter}) - - assert usdc.balanceOf(agent) >= amount, "Insufficient USDC balance" - - -def prepare_agent_for_usdt_payment(amount: int): - agent, usdt = interface.Agent(AGENT), interface.Usdt(USDT_TOKEN) - if usdt.balanceOf(agent) < amount: - usdt_owner = accounts.at("0xC6CDE7C39eB2f0F0095F41570af89eFC2C1Ea828", force=True) - usdt.issue(amount, {"from": usdt_owner}) - usdt.transfer(agent, amount, {"from": usdt_owner}) - - assert usdt.balanceOf(agent) >= amount, "Insufficient USDT balance" - - -def prepare_agent_for_susds_payment(amount: int): - agent, susds = interface.Agent(AGENT), interface.ERC20(SUSDS_TOKEN) - if susds.balanceOf(agent) < amount: - susds_whale = accounts.at("0xBc65ad17c5C0a2A4D159fa5a503f4992c7B545FE", force=True) - susds.transfer(agent, amount, {"from": susds_whale}) - - assert susds.balanceOf(agent) >= amount, "Insufficient sUSDS balance" - - -def prepare_agent_for_ldo_payment(amount: int): - agent, ldo = interface.Agent(AGENT), interface.ERC20(LDO_TOKEN) - assert ldo.balanceOf(agent) >= amount, "Insufficient LDO balance 🫡" - - -def prepare_agent_for_steth_payment(amount: int): - STETH_TRANSFER_MAX_DELTA = 2 - - agent, steth = interface.Agent(AGENT), interface.Lido(LIDO) - eth_whale = accounts.at("0x00000000219ab540356cBB839Cbe05303d7705Fa", force=True) - if steth.balanceOf(agent) < amount: - steth.submit(ZERO_ADDRESS, {"from": eth_whale, "value": amount + 2 * STETH_TRANSFER_MAX_DELTA}) - steth.transfer(agent, amount + STETH_TRANSFER_MAX_DELTA, {"from": eth_whale}) - assert steth.balanceOf(agent) >= amount, "Insufficient stETH balance" - - def usds_wrap_happy_path(stranger): USDC_FOR_TRANSFER = 1000 USDS_TOKEN = "0xdC035D45d973E3EC169d2276DDab16f1e407384F" @@ -1187,3 +1136,59 @@ def usds_wrap_happy_path(stranger): assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 chain.revert() + + +def prepare_agent_for_dai_payment(amount: int): + agent, dai = interface.Agent(AGENT), interface.Dai(DAI_TOKEN) + if dai.balanceOf(agent) < amount: + dai_ward_impersonated = accounts.at("0x9759A6Ac90977b93B58547b4A71c78317f391A28", force=True) + dai.mint(agent, amount, {"from": dai_ward_impersonated}) + + assert dai.balanceOf(agent) >= amount, f"Insufficient DAI balance" + + +def prepare_agent_for_usdc_payment(amount: int): + agent, usdc = interface.Agent(AGENT), interface.Usdc(USDC_TOKEN) + if usdc.balanceOf(agent) < amount: + usdc_minter = accounts.at("0x5B6122C109B78C6755486966148C1D70a50A47D7", force=True) + usdc_controller = accounts.at("0x79E0946e1C186E745f1352d7C21AB04700C99F71", force=True) + usdc_master_minter = interface.UsdcMasterMinter("0xE982615d461DD5cD06575BbeA87624fda4e3de17") + usdc_master_minter.incrementMinterAllowance(amount, {"from": usdc_controller}) + usdc.mint(agent, amount, {"from": usdc_minter}) + + assert usdc.balanceOf(agent) >= amount, "Insufficient USDC balance" + + +def prepare_agent_for_usdt_payment(amount: int): + agent, usdt = interface.Agent(AGENT), interface.Usdt(USDT_TOKEN) + if usdt.balanceOf(agent) < amount: + usdt_owner = accounts.at("0xC6CDE7C39eB2f0F0095F41570af89eFC2C1Ea828", force=True) + usdt.issue(amount, {"from": usdt_owner}) + usdt.transfer(agent, amount, {"from": usdt_owner}) + + assert usdt.balanceOf(agent) >= amount, "Insufficient USDT balance" + + +def prepare_agent_for_susds_payment(amount: int): + agent, susds = interface.Agent(AGENT), interface.ERC20(SUSDS_TOKEN) + if susds.balanceOf(agent) < amount: + susds_whale = accounts.at("0xBc65ad17c5C0a2A4D159fa5a503f4992c7B545FE", force=True) + susds.transfer(agent, amount, {"from": susds_whale}) + + assert susds.balanceOf(agent) >= amount, "Insufficient sUSDS balance" + + +def prepare_agent_for_ldo_payment(amount: int): + agent, ldo = interface.Agent(AGENT), interface.ERC20(LDO_TOKEN) + assert ldo.balanceOf(agent) >= amount, "Insufficient LDO balance 🫡" + + +def prepare_agent_for_steth_payment(amount: int): + STETH_TRANSFER_MAX_DELTA = 2 + + agent, steth = interface.Agent(AGENT), interface.Lido(STETH_TOKEN) + eth_whale = accounts.at("0x00000000219ab540356cBB839Cbe05303d7705Fa", force=True) + if steth.balanceOf(agent) < amount: + steth.submit(ZERO_ADDRESS, {"from": eth_whale, "value": amount + 2 * STETH_TRANSFER_MAX_DELTA}) + steth.transfer(agent, amount + STETH_TRANSFER_MAX_DELTA, {"from": eth_whale}) + assert steth.balanceOf(agent) >= amount, "Insufficient stETH balance" From 8d6e9050d4245bdbb7ad572c64e168610288fa40 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 27 Nov 2025 10:53:29 +0000 Subject: [PATCH 056/178] fix: sUSDS checks --- tests/test_2025_12_10.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index f86f8f4ef..82a8dad87 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -120,7 +120,7 @@ class TokenLimit(NamedTuple): MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 -TRP_ALREADY_SPENT_BEFORE = 2_708_709 * 10**18 +TRP_ALREADY_SPENT_BEFORE = 4_208_709 * 10**18 TRP_ALREADY_SPENT_AFTER = 0 TRP_LIMIT_AFTER = 15_000_000 * 10**18 TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC @@ -129,7 +129,6 @@ class TokenLimit(NamedTuple): ALLOWED_TOKENS_BEFORE = 3 ALLOWED_TOKENS_AFTER = 4 -ET_LIDO_LABS_STABLES_LIMIT = 15_000_000 # ============================== Tokens =================================== @@ -672,6 +671,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g prepare_agent_for_steth_payment(2_000 * 10**18) # check ET limits via Easy Track motion + ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) @@ -1127,13 +1127,19 @@ def usds_wrap_happy_path(stranger): assert susds_token.balanceOf(eoa.address) == susds_balance assert susds_token.balanceOf(AGENT) == initial_susds_agent_balance + # wait 1 year to accumulate interest on sUSDS + chain.sleep(365 * 24 * 3600) + chain.mine() + susds_token.drip({"from": eoa}) + INTEREST_RATE = 0.04 + # unwrap sUSDS to USDC susds_token.approve(PSM_VARIANT1_ACTIONS, susds_balance, {"from": eoa}) - psmVariant1Actions.withdrawAndSwap(eoa.address, USDC_FOR_TRANSFER * 10**6, USDC_FOR_TRANSFER * 10**18, {"from": eoa}) + psmVariant1Actions.withdrawAndSwap(eoa.address, USDC_FOR_TRANSFER * 10**6 * (1 + INTEREST_RATE), USDC_FOR_TRANSFER * 10**18 * (1 + INTEREST_RATE), {"from": eoa}) usdc_balance = usdc.balanceOf(eoa.address) - print("swapped", susds_balance / 10**18, "sUSDS to", usdc_balance / 10**6, "USDC") - assert susds_token.balanceOf(eoa.address) < 1.0 * 10**18 # dust - assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 + print("swapped", susds_balance / 10**18, "sUSDS to", usdc_balance / 10**6, "USDC, leftover:", susds_token.balanceOf(eoa.address) / 10**18, "sUSDS") + assert susds_token.balanceOf(eoa.address) < 5.0 * 10**18 # leftover from interest surplus + assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 * (1 + INTEREST_RATE) chain.revert() From 4c0eaa0100e58262f5ec7e37885d654012344827 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 27 Nov 2025 11:10:28 +0000 Subject: [PATCH 057/178] fix: sUSDS checks --- tests/test_2025_12_10.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 82a8dad87..d19acdac7 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -120,7 +120,6 @@ class TokenLimit(NamedTuple): MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 -TRP_ALREADY_SPENT_BEFORE = 4_208_709 * 10**18 TRP_ALREADY_SPENT_AFTER = 0 TRP_LIMIT_AFTER = 15_000_000 * 10**18 TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC @@ -789,8 +788,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() assert trp_limit_before == TRP_LIMIT_BEFORE assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS - assert trp_already_spent_amount_before == TRP_ALREADY_SPENT_BEFORE - assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - TRP_ALREADY_SPENT_BEFORE + assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - trp_already_spent_amount_before assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP From 2057609a1f0dd0a9e03b2d227ac7a978a88f2809 Mon Sep 17 00:00:00 2001 From: dry914 Date: Fri, 28 Nov 2025 18:02:53 +0300 Subject: [PATCH 058/178] tests: new mainnet deployments + rebaseNotifier tests --- configs/config_mainnet.py | 18 +++++++------- scripts/upgrade_2025_12_10_mainnet_v3.py | 2 +- tests/acceptance/test_locator.py | 2 +- .../test_oracle_report_with_notifier.py | 24 ++++++++++++------- tests/test_2025_12_10_mainnet_v3.py | 10 +++++--- utils/config.py | 4 ---- utils/dual_governance.py | 18 +++++++------- 7 files changed, 42 insertions(+), 36 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 39fb080f7..2f14e34d8 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -108,7 +108,7 @@ # LidoLocator LIDO_LOCATOR = "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" -LIDO_LOCATOR_IMPL = "0xE2833211Ceac805d35C4fD22c733cf3BF7F0Df54" # TODO fix after mainnet deployment +LIDO_LOCATOR_IMPL = "0x26329a3D4cF2F89923b5c4C14a25A2485cD01aA2" # TODO fix after mainnet deployment # Other upgrade addresses LIDO_V2_UPGRADE_TEMPLATE = "0xa818fF9EC93122Bf9401ab4340C42De638CD600a" @@ -121,7 +121,7 @@ WITHDRAWAL_CREDENTIALS = "0x010000000000000000000000b9d7934878b5fb9610b3fe8a5e441e8fad7e293f" # Lido -LIDO_IMPL = "0xaA5D5c3404678dfF147004Bf6849155f1448D261" # TODO fix after mainnet deployment +LIDO_IMPL = "0xD0b9826e0EAf6dE91CE7A6783Cd6fd137ae422Ec" # TODO fix after mainnet deployment # see Lido's proxy appId() LIDO_ARAGON_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" LIDO_MAX_STAKE_LIMIT_ETH = 150_000 @@ -201,7 +201,7 @@ EXIT_EVENTS_LOOKBACK_WINDOW_IN_SLOTS = 14 * 7200 # 14 days # OracleReportSanityChecker -ORACLE_REPORT_SANITY_CHECKER = "0x5d073dB5EcB9E006258bE05566e94ED0012daD44" # TODO fix after mainnet deployment +ORACLE_REPORT_SANITY_CHECKER = "0x4c42eF565ED49aDcC459a53C3abDCa86617E2f63" # TODO fix after mainnet deployment APPEARED_VALIDATORS_PER_DAY_LIMIT = 1800 EXITED_VALIDATORS_PER_DAY_LIMIT = 3600 ANNUAL_BALANCE_INCREASE_BP_LIMIT = 1000 # 10% @@ -216,7 +216,7 @@ CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 50 # Burner -BURNER = "0x29783959aC9E3Eb0659C208f69aA174e26b5e3dD" # TODO fix after mainnet deployment +BURNER = "0xD140f4f3C515E1a328F6804C5426d9e8b883ED50" # TODO fix after mainnet deployment TOTAL_NON_COVER_SHARES_BURNT = 32145684728326685744 TOTAL_COVER_SHARES_BURNT = 0 @@ -239,7 +239,7 @@ # AccountingOracle # and its corresponding HashConsensus ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" -ACCOUNTING_ORACLE_IMPL = "0xb840D4D4cA5C9ce5202AEd502A18BE9831042F21" # TODO fix after mainnet deployment +ACCOUNTING_ORACLE_IMPL = "0xb2295820F5286BE40c2da8102eB2cDB24aD608Be" # TODO fix after mainnet deployment HASH_CONSENSUS_FOR_AO = "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288" AO_EPOCHS_PER_FRAME = 225 AO_FAST_LANE_LENGTH_SLOTS = 100 @@ -364,7 +364,7 @@ OBOL_LIDO_SPLIT_IMPL = "0x2fB59065F049e0D0E3180C6312FA0FeB5Bbf0FE3" # Ethereum Part for Optimism bridge -L1_TOKEN_RATE_NOTIFIER = "0xe6793B9e4FbA7DE0ee833F9D02bba7DB5EB27823" +L1_TOKEN_RATE_NOTIFIER = "0x7c7419E1964188C6133051dF541cf2a525AA1038" # TODO fix after mainnet deployment L1_OPTIMISM_TOKEN_RATE_PUSHER = "0xd54c1c6413caac3477AC14b2a80D5398E3c32FfE" L1_OPTIMISM_TOKENS_BRIDGE = "0x76943C0D61395d8F2edF9060e1533529cAe05dE6" L1_OPTIMISM_TOKENS_BRIDGE_IMPL = "0x29C5c51A031165CE62F964966A6399b81165EFA4" @@ -526,7 +526,7 @@ # Lido V3 - stVaults ---------------------------------------------------------- # ----------------------------------------------------------------------------- -VAULT_HUB = "0x8Ba5200ffc0CE49993FB6446f636BF9Fc5a36FD7" # TODO fix after mainnet deployment +VAULT_HUB = "0xdcC04F506E24495E9F2599A7b214522647363669" # TODO fix after mainnet deployment -ACCOUNTING = "0x5f22363Da639c23cEee8b223aEc440A6710C3242" # TODO fix after mainnet deployment -ACCOUNTING_IMPL = "0xB58852416D3CA4722207F63dB7430261eDF8E16a" # TODO fix after mainnet deployment +ACCOUNTING = "0xc9158c756D2a510eaC85792C847798a30dE20D46" # TODO fix after mainnet deployment +ACCOUNTING_IMPL = "0x1bE2Ee7D32e3F9C4ecd5b6BfF69306ACb8Ab239e" # TODO fix after mainnet deployment diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index 49ecdc147..c2294166f 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -29,7 +29,7 @@ # ============================== Addresses =================================== -OMNIBUS_CONTRACT = "0xda9D35b108f9538F8025f1c692265b9451D42f8b" # TODO replace with the actual omnibus contract address +OMNIBUS_CONTRACT = "0x7e2ef38FeDFEc1e768E55D63cb0273a726d0a318" # TODO replace with the actual omnibus contract address # ============================= Description ================================== diff --git a/tests/acceptance/test_locator.py b/tests/acceptance/test_locator.py index 8a9d627ba..b39619003 100644 --- a/tests/acceptance/test_locator.py +++ b/tests/acceptance/test_locator.py @@ -44,7 +44,7 @@ def test_addresses(contract): contracts.oracle_report_sanity_checker, contracts.burner, contracts.withdrawal_queue, - "0x0000000000000000000000000000000000000000", # TODO contracts.postTokenRebaseReceiver, + contracts.token_rate_notifier, contracts.staking_router, contracts.vault_hub, ) diff --git a/tests/regression/test_oracle_report_with_notifier.py b/tests/regression/test_oracle_report_with_notifier.py index 44574f3d5..4b283a71e 100644 --- a/tests/regression/test_oracle_report_with_notifier.py +++ b/tests/regression/test_oracle_report_with_notifier.py @@ -1,16 +1,22 @@ import pytest from brownie import Contract, accounts, chain, interface, OpStackTokenRatePusherWithSomeErrorStub, web3, reverts from utils.test.oracle_report_helpers import oracle_report -from utils.config import contracts, get_deployer_account, network_name +from utils.config import ( + contracts, + get_deployer_account, + network_name, + L1_TOKEN_RATE_NOTIFIER, + WSTETH_TOKEN, + ACCOUNTING_ORACLE, + L1_OPTIMISM_CROSS_DOMAIN_MESSENGER, + L2_OPTIMISM_TOKEN_RATE_ORACLE, +) from utils.test.helpers import ZERO_ADDRESS, eth_balance from utils.evm_script import encode_error from typing import TypedDict, TypeVar, Any -WST_ETH = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" -ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" -L1_TOKEN_RATE_NOTIFIER = "0xe6793B9e4FbA7DE0ee833F9D02bba7DB5EB27823" # TODO fix after mainnet deployment -L1_CROSS_DOMAIN_MESSENGER = "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1" -L2_TOKEN_RATE_ORACLE = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" +# Use as mock for L2 TokenRateOracle +L2_TOKEN_RATE_ORACLE = WSTETH_TOKEN @pytest.fixture(scope="module") @@ -37,8 +43,8 @@ def test_oracle_report_revert(): """Test oracle report reverts when messenger is empty""" interface.TokenRateNotifier(L1_TOKEN_RATE_NOTIFIER) # load TokenRateNotifier contract ABI to catch correct error - web3.provider.make_request("hardhat_setCode", [L1_CROSS_DOMAIN_MESSENGER, "0x"]) - web3.provider.make_request("evm_setAccountCode", [L1_CROSS_DOMAIN_MESSENGER, "0x"]) + web3.provider.make_request("hardhat_setCode", [L1_OPTIMISM_CROSS_DOMAIN_MESSENGER, "0x"]) + web3.provider.make_request("evm_setAccountCode", [L1_OPTIMISM_CROSS_DOMAIN_MESSENGER, "0x"]) with reverts(encode_error("ErrorTokenRateNotifierRevertedWithNoData()")): oracle_report(cl_diff=0, report_el_vault=True, report_withdrawals_vault=False) @@ -55,7 +61,7 @@ def test_oracle_report_pushes_rate(): tokenRateOracle = interface.ITokenRateUpdatable(L2_TOKEN_RATE_ORACLE) - wstETH = interface.WstETH(WST_ETH) + wstETH = interface.WstETH(WSTETH_TOKEN) accountingOracle = interface.AccountingOracle(ACCOUNTING_ORACLE) tokenRate = wstETH.getStETHByWstETH(10**27) diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index 9e36fd953..6883ac473 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -12,7 +12,7 @@ from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id from utils.ipfs import get_lido_vote_cid_from_str -from utils.dual_governance import PROPOSAL_STATUS +from utils.dual_governance import PROPOSAL_STATUS, wait_for_target_time_to_satisfy_time_constrains from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event @@ -38,7 +38,8 @@ EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 EXPECTED_VOTE_EVENTS_COUNT = 10 -EXPECTED_DG_EVENTS_COUNT = 17 +EXPECTED_DG_EVENTS_COUNT = 18 +EXPECTED_DG_AGENT_EVENTS_COUNT = 17 # Events from Agent (excluding first DG submission event) IPFS_DESCRIPTION_HASH = "bafkreic4xuaowfowt7faxnngnzynv7biuo7guv4s4jrngngjzzxyz3up2i" @@ -155,6 +156,9 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: chain.sleep(timelock.getAfterScheduleDelay() + 1) + + wait_for_target_time_to_satisfy_time_constrains() + dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) display_dg_events(dg_tx) dg_events = group_dg_events_from_receipt( @@ -162,7 +166,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g timelock=EMERGENCY_PROTECTED_TIMELOCK, admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, ) - assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_AGENT_EVENTS_COUNT assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT # TODO validate all DG events diff --git a/utils/config.py b/utils/config.py index 897262b1f..b9afabf42 100644 --- a/utils/config.py +++ b/utils/config.py @@ -262,10 +262,6 @@ def sandbox(self) -> interface.SimpleDVT: def legacy_oracle(self) -> interface.LegacyOracle: return interface.LegacyOracle(LEGACY_ORACLE) - @property - def token_rate_notifier(self) -> interface.TokenRateNotifier: - return interface.LegacyOracle(L1_TOKEN_RATE_NOTIFIER) - @property def deposit_security_module_v1(self) -> interface.DepositSecurityModule: return interface.DepositSecurityModuleV1(DEPOSIT_SECURITY_MODULE_V1) diff --git a/utils/dual_governance.py b/utils/dual_governance.py index 69beabce2..f621d6747 100644 --- a/utils/dual_governance.py +++ b/utils/dual_governance.py @@ -87,7 +87,7 @@ def process_proposals(proposal_ids: Sequence[int]): if len(scheduled_proposals): chain.sleep(after_schedule_delay + 1) - wait_for_noon_utc_to_satisfy_time_constrains() + wait_for_target_time_to_satisfy_time_constrains() for proposal_id in scheduled_proposals: contracts.emergency_protected_timelock.execute(proposal_id, {"from": stranger}) @@ -174,20 +174,20 @@ def wait_for_time_window(from_hour_utc: int, to_hour_utc: int): chain.sleep(sleep_time) -def wait_for_noon_utc_to_satisfy_time_constrains(): +def wait_for_target_time_to_satisfy_time_constrains(): current_time = chain.time() - noon_offset = 12 * 60 * 60 - seconds_per_day = noon_offset * 2 + target_time = 16 * 60 * 60 # 16:00 UTC + seconds_per_day = 24 * 60 * 60 day_start = current_time - (current_time % seconds_per_day) - today_noon = day_start + noon_offset + today_target_time = day_start + target_time - if current_time >= today_noon: - target_noon = today_noon + seconds_per_day + if current_time >= today_target_time: + target_time = today_target_time + seconds_per_day else: - target_noon = today_noon + target_time = today_target_time - chain.sleep(target_noon - current_time) + chain.sleep(target_time - current_time) def is_proposal_executed(proposal_id: int) -> bool: From 0a86c3895a0220a7e7cee9dd44297c9fb05ea785 Mon Sep 17 00:00:00 2001 From: dry914 Date: Fri, 28 Nov 2025 18:50:43 +0300 Subject: [PATCH 059/178] test: fix pause test --- tests/regression/test_pause_resume.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/regression/test_pause_resume.py b/tests/regression/test_pause_resume.py index 46d29f424..3e2c0924d 100644 --- a/tests/regression/test_pause_resume.py +++ b/tests/regression/test_pause_resume.py @@ -269,20 +269,26 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): (report_tx, _) = oracle_report() # Note: why we use TransferShares event in this test - https://github.com/lidofinance/core/issues/1565 # print(report_tx.events["TransferShares"]) - module_index = 0 - simple_dvt_index = 1 - csm_index = 2 - if report_tx.events["TransferShares"][module_index]["to"] == burner.address: + # zero index - mint to accounting contract, 1 index - module, 2 index - simple dvt, 3 index - csm + module_index = 1 + simple_dvt_index = 2 + csm_index = 3 + + if report_tx.events["TransferShares"][module_index-1]["to"] == burner.address: module_index += 1 simple_dvt_index += 1 csm_index += 1 agent_index = module_index + 3 assert report_tx.events["TransferShares"][module_index]["to"] == module_address - assert report_tx.events["TransferShares"][module_index]["from"] == ZERO_ADDRESS + assert report_tx.events["TransferShares"][module_index]["from"] == contracts.accounting.address + assert report_tx.events["TransferShares"][simple_dvt_index]["to"] == contracts.simple_dvt.address + assert report_tx.events["TransferShares"][simple_dvt_index]["from"] == contracts.accounting.address + assert report_tx.events["TransferShares"][csm_index]["to"] == contracts.csm.address + assert report_tx.events["TransferShares"][csm_index]["from"] == contracts.accounting.address assert report_tx.events["TransferShares"][agent_index]["to"] == contracts.agent - assert report_tx.events["TransferShares"][agent_index]["from"] == ZERO_ADDRESS + assert report_tx.events["TransferShares"][agent_index]["from"] == contracts.accounting.address # the staking modules ids starts from 1 From 4f44854ac22c0d2e9ed048dc9f8e1f4bd75620c0 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 28 Nov 2025 18:40:16 +0000 Subject: [PATCH 060/178] fix: added variables --- tests/test_2025_12_10.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index d19acdac7..bb694f7b1 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -75,10 +75,12 @@ class TokenLimit(NamedTuple): LEGO_LDO_TRUSTED_CALLER = "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030" LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x00caAeF11EC545B192f16313F53912E453c91458" LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY = "0x97615f72c3428A393d65A84A3ea6BBD9ad6C0D74" +LEGO_LDO_SPENDABLE_BALANCE = 1_000_000 * 10**18 GAS_SUPPLY_STETH_TRUSTED_CALLER = "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53" GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x200dA0b6a9905A377CF8D469664C65dB267009d1" GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x49d1363016aA899bba09ae972a1BF200dDf8C55F" +GAS_SUPPLY_STETH_SPENDABLE_BALANCE = 1_000 * 10**18 LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" @@ -675,8 +677,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 1_000_000 * 10**18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 1_000 * 10**18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, LEGO_LDO_SPENDABLE_BALANCE, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, GAS_SUPPLY_STETH_SPENDABLE_BALANCE, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) # check Finance limits via Easy Track motion finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) From fa712b3a96e886c1cb83498fd0e9270b7264c87a Mon Sep 17 00:00:00 2001 From: dry914 Date: Sat, 29 Nov 2025 00:13:45 +0300 Subject: [PATCH 061/178] test: add V3 permisions checks --- configs/config_mainnet.py | 10 + interfaces/LazyOracle.json | 1144 +++++++++++++++ interfaces/OperatorGrid.json | 1582 ++++++++++++++++++++ interfaces/PredepositGuarantee.json | 2001 ++++++++++++++++++++++++++ tests/regression/test_permissions.py | 87 +- utils/config.py | 12 + 6 files changed, 4834 insertions(+), 2 deletions(-) create mode 100644 interfaces/LazyOracle.json create mode 100644 interfaces/OperatorGrid.json create mode 100644 interfaces/PredepositGuarantee.json diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 2f14e34d8..5b25c9a8c 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -530,3 +530,13 @@ ACCOUNTING = "0xc9158c756D2a510eaC85792C847798a30dE20D46" # TODO fix after mainnet deployment ACCOUNTING_IMPL = "0x1bE2Ee7D32e3F9C4ecd5b6BfF69306ACb8Ab239e" # TODO fix after mainnet deployment + +OPERATOR_GRID = "0x79e2685C1DD4756AC709a6CeE7C6cC960128B031" # TODO fix after mainnet deployment + +LAZY_ORACLE = "0x4fA3c917BB8f8CD9d056C5DDF0a38bd1834c43F9" # TODO fix after mainnet deployment + +PREDEPOSIT_GUARANTEE = "0x7B49b203A100E326B84886dCC0e2c426f9b8cbBd" # TODO fix after mainnet deployment + +VAULTS_ADAPTER = "0x8cDA09f41970A8f4416b6bA4696a2f09a6080c76" # TODO fix after mainnet deployment + +GATE_SEAL_V3 = "0x9c2D30177DB12334998EB554f5d4E6dD44458167" # TODO fix after mainnet deployment diff --git a/interfaces/LazyOracle.json b/interfaces/LazyOracle.json new file mode 100644 index 000000000..8e39d00b9 --- /dev/null +++ b/interfaces/LazyOracle.json @@ -0,0 +1,1144 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_lidoLocator", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "AdminCannotBeZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "feeIncrease", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeeIncrease", + "type": "uint256" + } + ], + "name": "CumulativeLidoFeesTooLarge", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "reportingFees", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "previousFees", + "type": "uint256" + } + ], + "name": "CumulativeLidoFeesTooLow", + "type": "error" + }, + { + "inputs": [], + "name": "InOutDeltaCacheIsOverwritten", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMaxLiabilityShares", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProof", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "feeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeeRate", + "type": "uint256" + } + ], + "name": "MaxLidoFeeRatePerSecondTooLarge", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "rewardRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxRewardRatio", + "type": "uint256" + } + ], + "name": "MaxRewardRatioTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "NotAuthorized", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "quarantinePeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQuarantinePeriod", + "type": "uint256" + } + ], + "name": "QuarantinePeriodTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "TotalValueTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "UnderflowInTotalValueCalculation", + "type": "error" + }, + { + "inputs": [], + "name": "VaultReportIsFreshEnough", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "delta", + "type": "uint256" + } + ], + "name": "QuarantineActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "delta", + "type": "uint256" + } + ], + "name": "QuarantineReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "QuarantineRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "totalValueReminder", + "type": "uint256" + } + ], + "name": "QuarantineUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "quarantinePeriod", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxRewardRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxLidoFeeRatePerSecond", + "type": "uint256" + } + ], + "name": "SanityParamsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "root", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "cid", + "type": "string" + } + ], + "name": "VaultsReportDataUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_LIDO_FEE_RATE_PER_SECOND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_QUARANTINE_PERIOD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_REWARD_RATIO", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPDATE_SANITY_PARAMS_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "_pubkeys", + "type": "bytes[]" + } + ], + "name": "batchValidatorStatuses", + "outputs": [ + { + "components": [ + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + }, + { + "internalType": "contract IStakingVault", + "name": "stakingVault", + "type": "address" + }, + { + "internalType": "address", + "name": "nodeOperator", + "type": "address" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorStatus[]", + "name": "batch", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + } + ], + "name": "batchVaultsInfo", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "aggregatedBalance", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "inOutDelta", + "type": "int256" + }, + { + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "liabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mintableStETH", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint16", + "name": "reserveRatioBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "forcedRebalanceThresholdBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "infraFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "reservationFeeBP", + "type": "uint16" + }, + { + "internalType": "bool", + "name": "pendingDisconnect", + "type": "bool" + } + ], + "internalType": "struct LazyOracle.VaultInfo[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMembers", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_quarantinePeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxRewardRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxLidoFeeRatePerSecond", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "latestReportData", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "treeRoot", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "reportCid", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestReportTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLidoFeeRatePerSecond", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxRewardRatioBP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quarantinePeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "quarantineValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "removeVaultQuarantine", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_vaultsDataTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_vaultsDataRefSlot", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_vaultsDataTreeRoot", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "_vaultsDataReportCid", + "type": "string" + } + ], + "name": "updateReportData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_quarantinePeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxRewardRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxLidoFeeRatePerSecond", + "type": "uint256" + } + ], + "name": "updateSanityParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_totalValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_cumulativeLidoFees", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_slashingReserve", + "type": "uint256" + }, + { + "internalType": "bytes32[]", + "name": "_proof", + "type": "bytes32[]" + } + ], + "name": "updateVaultData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultInfo", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "aggregatedBalance", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "inOutDelta", + "type": "int256" + }, + { + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "liabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mintableStETH", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint16", + "name": "reserveRatioBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "forcedRebalanceThresholdBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "infraFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "reservationFeeBP", + "type": "uint16" + }, + { + "internalType": "bool", + "name": "pendingDisconnect", + "type": "bool" + } + ], + "internalType": "struct LazyOracle.VaultInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultQuarantine", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pendingTotalValueIncrease", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalValueRemainder", + "type": "uint256" + } + ], + "internalType": "struct LazyOracle.QuarantineInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/OperatorGrid.json b/interfaces/OperatorGrid.json new file mode 100644 index 000000000..ddda111b1 --- /dev/null +++ b/interfaces/OperatorGrid.json @@ -0,0 +1,1582 @@ +[ + { + "inputs": [ + { + "internalType": "contract ILidoLocator", + "name": "_locator", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ArrayLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "CannotChangeToDefaultTier", + "type": "error" + }, + { + "inputs": [], + "name": "ConfirmExpiryOutOfBounds", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + } + ], + "name": "ForcedRebalanceThresholdTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "GroupExists", + "type": "error" + }, + { + "inputs": [], + "name": "GroupLimitExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "GroupNotExists", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxInfraFeeBP", + "type": "uint256" + } + ], + "name": "InfraFeeTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "valueBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxValueBP", + "type": "uint256" + } + ], + "name": "InvalidBasisPoints", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLiquidityFeeBP", + "type": "uint256" + } + ], + "name": "LiquidityFeeTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "NodeOperatorNotExists", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "operation", + "type": "string" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "NotAuthorized", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestedShareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tierShareLimit", + "type": "uint256" + } + ], + "name": "RequestedShareLimitTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestedSHareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vaultShares", + "type": "uint256" + } + ], + "name": "RequestedShareLimitTooLow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxReservationFeeBP", + "type": "uint256" + } + ], + "name": "ReservationFeeTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxReserveRatioBP", + "type": "uint256" + } + ], + "name": "ReserveRatioTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [], + "name": "SenderNotMember", + "type": "error" + }, + { + "inputs": [], + "name": "ShareLimitAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "TierAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "TierLimitExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "TierNotExists", + "type": "error" + }, + { + "inputs": [], + "name": "TierNotInOperatorGroup", + "type": "error" + }, + { + "inputs": [], + "name": "VaultAlreadySyncedWithTier", + "type": "error" + }, + { + "inputs": [], + "name": "VaultInJail", + "type": "error" + }, + { + "inputs": [], + "name": "VaultInJailAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "VaultNotConnected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "argument", + "type": "string" + } + ], + "name": "ZeroArgument", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroConfirmingRoles", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldConfirmExpiry", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newConfirmExpiry", + "type": "uint256" + } + ], + "name": "ConfirmExpirySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + } + ], + "name": "GroupAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + } + ], + "name": "GroupShareLimitUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "member", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "roleOrAddress", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "confirmTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expiryTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "RoleMemberConfirmed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "name": "TierAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + } + ], + "name": "TierChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "name": "TierUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isInJail", + "type": "bool" + } + ], + "name": "VaultJailStatusUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_TIER_ID", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_TIER_OPERATOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_CONFIRM_EXPIRY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_CONFIRM_EXPIRY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REGISTRY_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "_tierIds", + "type": "uint256[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "internalType": "struct TierParams[]", + "name": "_tierParams", + "type": "tuple[]" + } + ], + "name": "alterTiers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_requestedTierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_requestedShareLimit", + "type": "uint256" + } + ], + "name": "changeTier", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_callData", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + } + ], + "name": "confirmation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "effectiveShareLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConfirmExpiry", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMembers", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "group", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "liabilityShares", + "type": "uint96" + }, + { + "internalType": "uint256[]", + "name": "tierIds", + "type": "uint256[]" + } + ], + "internalType": "struct OperatorGrid.Group", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "internalType": "struct TierParams", + "name": "_defaultTierParams", + "type": "tuple" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isVaultInJail", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "nodeOperatorAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nodeOperatorCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "onBurnedShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "_overrideLimits", + "type": "bool" + } + ], + "name": "onMintedShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shareLimit", + "type": "uint256" + } + ], + "name": "registerGroup", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "internalType": "struct TierParams[]", + "name": "_tiers", + "type": "tuple[]" + } + ], + "name": "registerTiers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "resetVaultTier", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newConfirmExpiry", + "type": "uint256" + } + ], + "name": "setConfirmExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isInJail", + "type": "bool" + } + ], + "name": "setVaultJailStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "syncTier", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_tierId", + "type": "uint256" + } + ], + "name": "tier", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "liabilityShares", + "type": "uint96" + }, + { + "internalType": "uint16", + "name": "reserveRatioBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "forcedRebalanceThresholdBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "infraFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "reservationFeeBP", + "type": "uint16" + } + ], + "internalType": "struct OperatorGrid.Tier", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tiersCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shareLimit", + "type": "uint256" + } + ], + "name": "updateGroupShareLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reservationFeeBP", + "type": "uint256" + } + ], + "name": "updateVaultFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_requestedShareLimit", + "type": "uint256" + } + ], + "name": "updateVaultShareLimit", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultTierInfo", + "outputs": [ + { + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/PredepositGuarantee.json b/interfaces/PredepositGuarantee.json new file mode 100644 index 000000000..ee708d803 --- /dev/null +++ b/interfaces/PredepositGuarantee.json @@ -0,0 +1,2001 @@ +[ + { + "inputs": [ + { + "internalType": "bytes4", + "name": "_genesisForkVersion", + "type": "bytes4" + }, + { + "internalType": "GIndex", + "name": "_gIFirstValidator", + "type": "bytes32" + }, + { + "internalType": "GIndex", + "name": "_gIFirstValidatorAfterChange", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "_pivotSlot", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ArrayLengthsNotMatch", + "type": "error" + }, + { + "inputs": [], + "name": "CompensateFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyDeposits", + "type": "error" + }, + { + "inputs": [], + "name": "IndexOutOfRange", + "type": "error" + }, + { + "inputs": [], + "name": "InputHasInfinityPoints", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDepositYLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPubkeyLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSlot", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "InvalidTopUpAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "InvalidValidatorStage", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "locked", + "type": "uint256" + } + ], + "name": "LockedIsNotZero", + "type": "error" + }, + { + "inputs": [], + "name": "NotDepositor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "unlocked", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "NotEnoughUnlocked", + "type": "error" + }, + { + "inputs": [], + "name": "NotGuarantor", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "NotStakingVaultOwner", + "type": "error" + }, + { + "inputs": [], + "name": "NothingToRefund", + "type": "error" + }, + { + "inputs": [], + "name": "PauseUntilMustBeInFuture", + "type": "error" + }, + { + "inputs": [], + "name": "PausedExpected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "depositAmount", + "type": "uint256" + } + ], + "name": "PredepositAmountInvalid", + "type": "error" + }, + { + "inputs": [], + "name": "RefundFailed", + "type": "error" + }, + { + "inputs": [], + "name": "ResumedExpected", + "type": "error" + }, + { + "inputs": [], + "name": "RootNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "SameDepositor", + "type": "error" + }, + { + "inputs": [], + "name": "SameGuarantor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "ValidatorNotActivated", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + } + ], + "name": "ValidatorNotEligibleForActivation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "ValidatorNotNew", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "ValidatorNotPreDeposited", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "ValidatorNotProven", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "ValueNotMultipleOfPredepositAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "WithdrawalCredentialsInvalidVersion", + "type": "error" + }, + { + "inputs": [], + "name": "WithdrawalCredentialsMatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "WithdrawalCredentialsMisformed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalCredentialsAddress", + "type": "address" + } + ], + "name": "WithdrawalCredentialsMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "WithdrawalFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "argument", + "type": "string" + } + ], + "name": "ZeroArgument", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPauseDuration", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "total", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "locked", + "type": "uint128" + } + ], + "name": "BalanceLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "BalanceRefunded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "BalanceToppedUp", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "total", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "locked", + "type": "uint128" + } + ], + "name": "BalanceUnlocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "BalanceWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newDepositor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "prevDepositor", + "type": "address" + } + ], + "name": "DepositorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guarantor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "GuarantorRefundAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guarantor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "GuarantorRefundClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGuarantor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "prevGuarantor", + "type": "address" + } + ], + "name": "GuarantorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "ValidatorActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "guaranteeTotal", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "guaranteeLocked", + "type": "uint256" + } + ], + "name": "ValidatorCompensated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "ValidatorPreDeposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "ValidatorProven", + "type": "event" + }, + { + "inputs": [], + "name": "ACTIVATION_DEPOSIT_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BEACON_ROOTS", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEPOSIT_DOMAIN", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GI_FIRST_VALIDATOR_CURR", + "outputs": [ + { + "internalType": "GIndex", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GI_FIRST_VALIDATOR_PREV", + "outputs": [ + { + "internalType": "GIndex", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GI_PUBKEY_WC_PARENT", + "outputs": [ + { + "internalType": "GIndex", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GI_STATE_ROOT", + "outputs": [ + { + "internalType": "GIndex", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_SUPPORTED_WC_VERSION", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_TOPUP_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_SUPPORTED_WC_VERSION", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_INFINITELY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PIVOT_SLOT", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PREDEPOSIT_AMOUNT", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_pubkey", + "type": "bytes" + } + ], + "name": "activateValidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + } + ], + "name": "claimGuarantorRefund", + "outputs": [ + { + "internalType": "uint256", + "name": "claimedEther", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_guarantor", + "type": "address" + } + ], + "name": "claimableRefund", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getResumeSinceTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMembers", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_defaultAdmin", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "nodeOperatorBalance", + "outputs": [ + { + "components": [ + { + "internalType": "uint128", + "name": "total", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "locked", + "type": "uint128" + } + ], + "internalType": "struct PredepositGuarantee.NodeOperatorBalance", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "nodeOperatorDepositor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "nodeOperatorGuarantor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_duration", + "type": "uint256" + } + ], + "name": "pauseFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pauseUntilInclusive", + "type": "uint256" + } + ], + "name": "pauseUntil", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IStakingVault", + "name": "_vault", + "type": "address" + } + ], + "name": "pendingActivations", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IStakingVault", + "name": "_stakingVault", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + } + ], + "internalType": "struct IStakingVault.Deposit[]", + "name": "_deposits", + "type": "tuple[]" + }, + { + "components": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "b", + "type": "bytes32" + } + ], + "internalType": "struct BLS12_381.Fp", + "name": "pubkeyY", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "c0_a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c0_b", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c1_a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c1_b", + "type": "bytes32" + } + ], + "internalType": "struct BLS12_381.Fp2", + "name": "signatureY", + "type": "tuple" + } + ], + "internalType": "struct BLS12_381.DepositY[]", + "name": "_depositsY", + "type": "tuple[]" + } + ], + "name": "predeposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_invalidWithdrawalCredentials", + "type": "bytes32" + } + ], + "name": "proveInvalidValidatorWC", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + }, + { + "internalType": "contract IStakingVault", + "name": "_stakingVault", + "type": "address" + } + ], + "name": "proveUnknownValidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness[]", + "name": "_witnesses", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + } + ], + "name": "proveWCActivateAndTopUpValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + } + ], + "name": "proveWCAndActivate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resume", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newDepositor", + "type": "address" + } + ], + "name": "setNodeOperatorDepositor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newGuarantor", + "type": "address" + } + ], + "name": "setNodeOperatorGuarantor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct PredepositGuarantee.ValidatorTopUp[]", + "name": "_topUps", + "type": "tuple[]" + } + ], + "name": "topUpExistingValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "topUpNodeOperatorBalance", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "unlockedBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "unlocked", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "validatePubKeyWCProof", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_validatorPubkey", + "type": "bytes" + } + ], + "name": "validatorStatus", + "outputs": [ + { + "components": [ + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + }, + { + "internalType": "contract IStakingVault", + "name": "stakingVault", + "type": "address" + }, + { + "internalType": "address", + "name": "nodeOperator", + "type": "address" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorStatus", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + } + ], + "internalType": "struct IStakingVault.Deposit", + "name": "_deposit", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "b", + "type": "bytes32" + } + ], + "internalType": "struct BLS12_381.Fp", + "name": "pubkeyY", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "c0_a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c0_b", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c1_a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c1_b", + "type": "bytes32" + } + ], + "internalType": "struct BLS12_381.Fp2", + "name": "signatureY", + "type": "tuple" + } + ], + "internalType": "struct BLS12_381.DepositY", + "name": "_depositsY", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "verifyDepositMessage", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + } + ], + "name": "withdrawNodeOperatorBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/tests/regression/test_permissions.py b/tests/regression/test_permissions.py index 84b85122f..c144d7d3c 100644 --- a/tests/regression/test_permissions.py +++ b/tests/regression/test_permissions.py @@ -63,7 +63,14 @@ L1_EMERGENCY_BRAKES_MULTISIG, DUAL_GOVERNANCE_EXECUTORS, RESEAL_MANAGER, - INSURANCE_FUND + INSURANCE_FUND, + VAULT_HUB, + OPERATOR_GRID, + LAZY_ORACLE, + ACCOUNTING, + PREDEPOSIT_GUARANTEE, + VAULTS_ADAPTER, + GATE_SEAL_V3 ) @@ -494,6 +501,77 @@ def protocol_permissions(): "PAUSE_ROLE": [contracts.voting, L1_EMERGENCY_BRAKES_MULTISIG], "UNPAUSE_ROLE": [contracts.voting], }, + }, + VAULT_HUB: { + "contract_name": "VaultHub", + "contract": contracts.vault_hub, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": { + "DEFAULT_ADMIN_ROLE": [contracts.agent], + "VAULT_MASTER_ROLE": [contracts.agent], + "REDEMPTION_MASTER_ROLE": [], + "VALIDATOR_EXIT_ROLE": [VAULTS_ADAPTER], + "BAD_DEBT_MASTER_ROLE": [VAULTS_ADAPTER], + "PAUSE_ROLE": [GATE_SEAL_V3, RESEAL_MANAGER], + "RESUME_ROLE": [RESEAL_MANAGER], + }, + "role_preimages": { + "VAULT_MASTER_ROLE": "vaults.VaultHub.VaultMasterRole", + "REDEMPTION_MASTER_ROLE": "vaults.VaultHub.RedemptionMasterRole", + "VALIDATOR_EXIT_ROLE": "vaults.VaultHub.ValidatorExitRole", + "BAD_DEBT_MASTER_ROLE": "vaults.VaultHub.BadDebtMasterRole", + "PAUSE_ROLE": "PausableUntilWithRoles.PauseRole", + "RESUME_ROLE": "PausableUntilWithRoles.ResumeRole", + }, + }, + OPERATOR_GRID: { + "contract_name": "OperatorGrid", + "contract": contracts.operator_grid, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": { + "DEFAULT_ADMIN_ROLE": [contracts.agent], + "REGISTRY_ROLE": [contracts.agent, EASYTRACK_EVMSCRIPT_EXECUTOR, VAULTS_ADAPTER], + }, + "role_preimages": { + "REGISTRY_ROLE": "vaults.OperatorsGrid.Registry", + }, + }, + LAZY_ORACLE: { + "contract_name": "LazyOracle", + "contract": contracts.lazy_oracle, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": { + "DEFAULT_ADMIN_ROLE": [contracts.agent], + "UPDATE_SANITY_PARAMS_ROLE": [], + }, + "role_preimages": { + "UPDATE_SANITY_PARAMS_ROLE": "vaults.LazyOracle.UpdateSanityParams", + }, + }, + ACCOUNTING: { + "contract_name": "Accounting", + "contract": contracts.accounting, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": {}, + }, + PREDEPOSIT_GUARANTEE: { + "contract_name": "PredepositGuarantee", + "contract": contracts.predeposit_guarantee, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": { + "DEFAULT_ADMIN_ROLE": [contracts.agent], + "PAUSE_ROLE": [GATE_SEAL_V3, RESEAL_MANAGER], + "RESUME_ROLE": [RESEAL_MANAGER], + }, + "role_preimages": { + "PAUSE_ROLE": "PausableUntilWithRoles.PauseRole", + "RESUME_ROLE": "PausableUntilWithRoles.ResumeRole", + }, } } @@ -559,7 +637,12 @@ def test_protocol_permissions(protocol_permissions): ) for role, holders in permissions_config["roles"].items(): - role_keccak = web3.keccak(text=role).hex() if role != "DEFAULT_ADMIN_ROLE" else ZERO_BYTES32.hex() + # Use custom preimage if specified, otherwise use role name + role_preimage = role + if "role_preimages" in permissions_config and role in permissions_config["role_preimages"]: + role_preimage = permissions_config["role_preimages"][role] + + role_keccak = web3.keccak(text=role_preimage).hex() if role != "DEFAULT_ADMIN_ROLE" else ZERO_BYTES32.hex() role_signature = permissions_config["contract"].signatures[role] assert permissions_config["contract"].get_method_object(role_signature)() == role_keccak diff --git a/utils/config.py b/utils/config.py index b9afabf42..8619f1220 100644 --- a/utils/config.py +++ b/utils/config.py @@ -318,6 +318,18 @@ def vault_hub(self) -> interface.VaultHub: def accounting(self) -> interface.Accounting: return interface.Accounting(ACCOUNTING) + @property + def operator_grid(self) -> interface.OperatorGrid: + return interface.OperatorGrid(OPERATOR_GRID) + + @property + def lazy_oracle(self) -> interface.LazyOracle: + return interface.LazyOracle(LAZY_ORACLE) + + @property + def predeposit_guarantee(self) -> interface.PredepositGuarantee: + return interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) + @property def lido_locator(self) -> interface.LidoLocator: return interface.LidoLocator(LIDO_LOCATOR) From 1c1186288be923c8bd26a47412b6343f4878665c Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 29 Nov 2025 09:26:08 +0000 Subject: [PATCH 062/178] feat: add 4 stonks factories --- scripts/vote_2025_12_10.py | 45 +++++++++ tests/test_2025_12_10.py | 109 +++++++++++++++++++++- utils/test/easy_track_helpers.py | 17 ++-- utils/test/event_validators/easy_track.py | 4 +- 4 files changed, 162 insertions(+), 13 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index d57a08273..5e7faedb8 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -25,6 +25,11 @@ 6. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 7. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS +VII. Add allowed recipients management factories for Stonks to Easy Track +8. Add Stonks stETH AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 +9. Add Stonks stETH RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 +10. Add Stonks stablecoins AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 +11. Add Stonks stablecoins RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ @@ -41,6 +46,7 @@ from utils.dual_governance import submit_proposals from utils.agent import agent_forward from utils.permissions import encode_permission_revoke, encode_permission_grant_p +from utils.easy_track import add_evmscript_factory, create_permissions from utils.allowed_recipients_registry import ( unsafe_set_spent_amount, set_limit_parameters, @@ -62,6 +68,14 @@ class TokenLimit(NamedTuple): STABLECOINS_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY = "0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22" +STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY = "0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A" +STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0" + +STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY = "0x56bcff69e1d06e18C46B65C00D41B4ae82890184" +STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY = "0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD" +STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY = "0x3f0534CCcFb952470775C516DC2eff8396B8A368" + # ============================== Roles =================================== CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" @@ -200,6 +214,9 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: staking_router = interface.StakingRouter(STAKING_ROUTER) stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) + stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) + stonks_stablescoints_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) + dg_items = [ agent_forward([ # 1.1. Change Curated Module (MODULE_ID = 1) module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 @@ -301,6 +318,34 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: params=amount_limits(), ), ), + ( + "8. Add Stonks stETH AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0", + add_evmscript_factory( + factory=STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_steth_allowed_recipients_registry, "addRecipient"), + ), + ), + ( + "9. Add Stonks stETH RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0", + add_evmscript_factory( + factory=STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_steth_allowed_recipients_registry, "removeRecipient"), + ), + ), + ( + "10. Add Stonks stablecoins AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", + add_evmscript_factory( + factory=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_stablescoints_allowed_recipients_registry, "addRecipient"), + ), + ), + ( + "11. Add Stonks stablecoins RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", + add_evmscript_factory( + factory=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_stablescoints_allowed_recipients_registry, "removeRecipient"), + ), + ), ) return vote_desc_items, call_script_items diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index bb694f7b1..d6919544a 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -4,12 +4,18 @@ from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 from brownie.network.transaction import TransactionReceipt from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op -from utils.test.easy_track_helpers import create_and_enact_payment_motion +from utils.test.easy_track_helpers import ( + create_and_enact_payment_motion, + create_and_enact_add_recipient_motion, + create_and_enact_remove_recipient_motion, + check_add_and_remove_recipient_with_voting, +) from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id from utils.agent import agent_forward from utils.ipfs import get_lido_vote_cid_from_str +from utils.easy_track import create_permissions from utils.dual_governance import PROPOSAL_STATUS from utils.test.event_validators.allowed_tokens_registry import validate_add_token_event from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event @@ -39,6 +45,10 @@ validate_permission_grantp_event, validate_permission_revoke_event, ) +from utils.test.event_validators.easy_track import ( + validate_evmscript_factory_added_event, + EVMScriptFactoryAdded, +) class TokenLimit(NamedTuple): @@ -82,6 +92,15 @@ class TokenLimit(NamedTuple): GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x49d1363016aA899bba09ae972a1BF200dDf8C55F" GAS_SUPPLY_STETH_SPENDABLE_BALANCE = 1_000 * 10**18 +STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY = "0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22" +STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY = "0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A" +STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0" +STONKS_STETH_MS = "0xa02FC823cCE0D016bD7e17ac684c9abAb2d6D647" + +STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY = "0x56bcff69e1d06e18C46B65C00D41B4ae82890184" +STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY = "0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD" +STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY = "0x3f0534CCcFb952470775C516DC2eff8396B8A368" + LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" @@ -131,6 +150,9 @@ class TokenLimit(NamedTuple): ALLOWED_TOKENS_BEFORE = 3 ALLOWED_TOKENS_AFTER = 4 +ET_FACTORIES_LEN_BEFORE = 33 +ET_FACTORIES_LEN_AFTER = ET_FACTORIES_LEN_BEFORE + 4 + # ============================== Tokens =================================== MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" @@ -146,7 +168,7 @@ class TokenLimit(NamedTuple): # ============================== Voting =================================== EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 -EXPECTED_VOTE_EVENTS_COUNT = 7 +EXPECTED_VOTE_EVENTS_COUNT = 11 EXPECTED_DG_EVENTS_COUNT = 4 IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" @@ -406,6 +428,9 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) acl = interface.ACL(ACL) stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) + stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) + stonks_stablecoins_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) + easy_track = interface.EasyTrack(EASY_TRACK) # ========================================================================= @@ -473,6 +498,14 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert op == amount_limits_before()[i].op.value assert val == amount_limits_before()[i].value + # Items 8-11 + et_factories_before = easy_track.getEVMScriptFactories() + assert len(et_factories_before) == ET_FACTORIES_LEN_BEFORE + assert STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY not in et_factories_before + assert STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY not in et_factories_before + assert STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY not in et_factories_before + assert STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY not in et_factories_before + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH @@ -594,6 +627,46 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit + 1], ) + # Items 8-11 + et_factories_after = easy_track.getEVMScriptFactories() + assert len(et_factories_after) == ET_FACTORIES_LEN_AFTER + assert STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY in et_factories_after + assert STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY in et_factories_after + assert STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY in et_factories_after + assert STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY in et_factories_after + + # scenario tests for new factories + chain.snapshot() + check_add_and_remove_recipient_with_voting(stonks_steth_allowed_recipients_registry, helpers, ldo_holder, voting) + check_add_and_remove_recipient_with_voting(stonks_stablecoins_allowed_recipients_registry, helpers, ldo_holder, voting) + chain.revert() + #create_and_enact_add_recipient_motion( + # easy_track, + # "0xe2A682A9722354D825d1BbDF372cC86B2ea82c8C", + # interface.AllowedRecipientRegistry("0xdc7300622948a7AdaF339783F6991F9cdDD79776"), + # "0x1F809D2cb72a5Ab13778811742050eDa876129b6", + # stranger, + # "New recipient", + # ldo_holder, + #) + #create_and_enact_payment_motion( + # easy_track, + # rewards_share_multisig, + # rewards_share_topup_factory, + # stETH_token, + # [stranger], + # [10 * 10**18], + # stranger, + #) + #create_and_enact_remove_recipient_motion( + # easy_track, + # rewards_share_multisig, + # rewards_share_registry, + # rewards_share_remove_recipient_factory, + # stranger, + # ldo_holder, + #) + assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT if EXPECTED_DG_PROPOSAL_ID is not None: @@ -658,6 +731,38 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g params=amount_limits_after(), emitted_by=ACL, ) + validate_evmscript_factory_added_event( + event=vote_events[7], + p=EVMScriptFactoryAdded( + factory_addr=STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_steth_allowed_recipients_registry, "addRecipient"), + ), + emitted_by=EASY_TRACK, + ) + validate_evmscript_factory_added_event( + event=vote_events[8], + p=EVMScriptFactoryAdded( + factory_addr=STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_steth_allowed_recipients_registry, "removeRecipient"), + ), + emitted_by=EASY_TRACK, + ) + validate_evmscript_factory_added_event( + event=vote_events[9], + p=EVMScriptFactoryAdded( + factory_addr=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "addRecipient"), + ), + emitted_by=EASY_TRACK, + ) + validate_evmscript_factory_added_event( + event=vote_events[10], + p=EVMScriptFactoryAdded( + factory_addr=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "removeRecipient"), + ), + emitted_by=EASY_TRACK, + ) # ======================================================================= # =========================== Scenario tests ============================ diff --git a/utils/test/easy_track_helpers.py b/utils/test/easy_track_helpers.py index 3a0158df6..96d154f3c 100644 --- a/utils/test/easy_track_helpers.py +++ b/utils/test/easy_track_helpers.py @@ -146,26 +146,25 @@ def check_add_and_remove_recipient_with_voting(registry, helpers, ldo_holder, da assert not registry.isRecipientAllowed(recipient_candidate) - call_script_items = [ - agent_forward( - [ - ( - registry.address, - registry.addRecipient.encode_input(recipient_candidate, title), - ) - ] + vote_input = [ + ( + registry.address, + registry.addRecipient.encode_input(recipient_candidate, title), ) ] + + call_script_items = submit_proposals([([agent_forward(vote_input)], "")]) vote_desc_items = ["Add recipient"] vote_items = bake_vote_items(vote_desc_items, call_script_items) vote_id = create_vote(vote_items, {"from": ldo_holder})[0] - helpers.execute_vote( + vote_tx = helpers.execute_vote( vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, ) + process_proposals([vote_tx.events["ProposalSubmitted"][1]["proposalId"]]) assert registry.isRecipientAllowed(recipient_candidate) assert len(registry.getAllowedRecipients()) == recipients_length_before + 1, "Wrong whitelist length" diff --git a/utils/test/event_validators/easy_track.py b/utils/test/event_validators/easy_track.py index d9d0b6ce0..a6952c21f 100644 --- a/utils/test/event_validators/easy_track.py +++ b/utils/test/event_validators/easy_track.py @@ -13,9 +13,9 @@ class EVMScriptFactoryAdded(NamedTuple): def validate_evmscript_factory_added_event( event: EventDict, p: EVMScriptFactoryAdded, - _events_chain=["LogScriptCall", "EVMScriptFactoryAdded"], - emitted_by: str | None = None, + emitted_by: str, ): + _events_chain=["LogScriptCall", "EVMScriptFactoryAdded"] validate_events_chain([e.name for e in event], _events_chain) assert event.count("EVMScriptFactoryAdded") == 1 From 51595f3d95237d862ba43d4208d8803e363a9aac Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 29 Nov 2025 10:07:41 +0000 Subject: [PATCH 063/178] feat: add stonks perms --- scripts/vote_2025_12_10.py | 64 +++++++++++++++---- tests/test_2025_12_10.py | 125 ++++++++++++++++++++++++++++--------- 2 files changed, 149 insertions(+), 40 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 5e7faedb8..b92d9aeca 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -9,23 +9,29 @@ 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 III. Reset Easy Track TRP limit -# 1.3. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO -# 1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months +1.3. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO +1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + +IV. Grant Stonks allowed recipients management permissions to Easy Track EVM Script Executor +1.5 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 +1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 +1.7 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 +1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 === NON-DG ITEMS === -IV. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig +V. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig 2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 -V. Add sUSDS to stablecoins Allowed Tokens Registry +VI. Add sUSDS to stablecoins Allowed Tokens Registry 3. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e 4. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca 5. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e -VI. Add sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance +VII. Add sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance 6. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 7. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS -VII. Add allowed recipients management factories for Stonks to Easy Track +VIII. Add Stonks allowed recipients management factories to Easy Track 8. Add Stonks stETH AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 9. Add Stonks stETH RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 10. Add Stonks stablecoins AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 @@ -215,7 +221,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) - stonks_stablescoints_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) + stonks_stablecoins_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) dg_items = [ agent_forward([ @@ -260,15 +266,51 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: registry_address=ET_TRP_REGISTRY, ), ]), + agent_forward([ + # 1.5 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 + ( + stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text="ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE")), + ET_EVM_SCRIPT_EXECUTOR, + ) + ), + ]), + agent_forward([ + # 1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 + ( + stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text="ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE")), + ET_EVM_SCRIPT_EXECUTOR, + ) + ), + ]), + agent_forward([ + # 1.7 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 + ( + stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text="REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE")), + ET_EVM_SCRIPT_EXECUTOR, + ) + ), + ]), + agent_forward([ + # 1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 + ( + stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text="REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE")), + ET_EVM_SCRIPT_EXECUTOR, + ) + ), + ]), ] dg_call_script = submit_proposals([ - (dg_items, "Upgrade Lido Protocol to change Curated Module fees, raise SDVT stake share limit and reset Easy Track TRP limit") + (dg_items, "Change Curated Module fees, raise SDVT stake share limit, reset Easy Track TRP limit and grant Stonks allowed recipients management permissions to Easy Track EVM Script Executor") ]) vote_desc_items, call_script_items = zip( ( - "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit and reset Easy Track TRP limit", + "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit, reset Easy Track TRP limit and grant Stonks allowed recipients management permissions to Easy Track EVM Script Executor", dg_call_script[0] ), ( @@ -336,14 +378,14 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: "10. Add Stonks stablecoins AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", add_evmscript_factory( factory=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablescoints_allowed_recipients_registry, "addRecipient"), + permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "addRecipient"), ), ), ( "11. Add Stonks stablecoins RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", add_evmscript_factory( factory=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablescoints_allowed_recipients_registry, "removeRecipient"), + permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "removeRecipient"), ), ), ) diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index d6919544a..343cf2118 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -95,11 +95,13 @@ class TokenLimit(NamedTuple): STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY = "0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22" STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY = "0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A" STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0" -STONKS_STETH_MS = "0xa02FC823cCE0D016bD7e17ac684c9abAb2d6D647" +STONKS_STETH_TOPUP_FACTORY = "0x6e04aED774B7c89BB43721AcDD7D03C872a51B69" +STONKS_MS = "0xa02FC823cCE0D016bD7e17ac684c9abAb2d6D647" STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY = "0x56bcff69e1d06e18C46B65C00D41B4ae82890184" STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY = "0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD" STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY = "0x3f0534CCcFb952470775C516DC2eff8396B8A368" +STONKS_STABLECOINS_TOPUP_FACTORY = "0x0d2aefA542aFa8d9D1Ec35376068B88042FEF5f6" LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" @@ -169,7 +171,7 @@ class TokenLimit(NamedTuple): EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 EXPECTED_VOTE_EVENTS_COUNT = 11 -EXPECTED_DG_EVENTS_COUNT = 4 +EXPECTED_DG_EVENTS_COUNT = 8 IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" @@ -359,6 +361,8 @@ def amount_limits_after() -> List[Param]: def dual_governance_proposal_calls(): staking_router = interface.StakingRouter(STAKING_ROUTER) + stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) + stonks_stablecoins_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) dg_items = [ agent_forward([ @@ -399,6 +403,38 @@ def dual_governance_proposal_calls(): registry_address=ET_TRP_REGISTRY, ), ]), + agent_forward([ + ( + stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text="ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE")), + ET_EVM_SCRIPT_EXECUTOR, + ) + ), + ]), + agent_forward([ + ( + stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text="ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE")), + ET_EVM_SCRIPT_EXECUTOR, + ) + ), + ]), + agent_forward([ + ( + stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text="REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE")), + ET_EVM_SCRIPT_EXECUTOR, + ) + ), + ]), + agent_forward([ + ( + stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text="REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE")), + ET_EVM_SCRIPT_EXECUTOR, + ) + ), + ]), ] # Convert each dg_item to the expected format @@ -640,32 +676,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g check_add_and_remove_recipient_with_voting(stonks_steth_allowed_recipients_registry, helpers, ldo_holder, voting) check_add_and_remove_recipient_with_voting(stonks_stablecoins_allowed_recipients_registry, helpers, ldo_holder, voting) chain.revert() - #create_and_enact_add_recipient_motion( - # easy_track, - # "0xe2A682A9722354D825d1BbDF372cC86B2ea82c8C", - # interface.AllowedRecipientRegistry("0xdc7300622948a7AdaF339783F6991F9cdDD79776"), - # "0x1F809D2cb72a5Ab13778811742050eDa876129b6", - # stranger, - # "New recipient", - # ldo_holder, - #) - #create_and_enact_payment_motion( - # easy_track, - # rewards_share_multisig, - # rewards_share_topup_factory, - # stETH_token, - # [stranger], - # [10 * 10**18], - # stranger, - #) - #create_and_enact_remove_recipient_motion( - # easy_track, - # rewards_share_multisig, - # rewards_share_registry, - # rewards_share_remove_recipient_factory, - # stranger, - # ldo_holder, - #) assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT @@ -678,7 +688,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g proposal_id=EXPECTED_DG_PROPOSAL_ID, proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="Upgrade Lido Protocol to change Curated Module fees, raise SDVT stake share limit and reset Easy Track TRP limit", + metadata="Change Curated Module fees, raise SDVT stake share limit, reset Easy Track TRP limit and grant Stonks allowed recipients management permissions to Easy Track EVM Script Executor", proposal_calls=dual_governance_proposal_calls, ) @@ -1019,6 +1029,63 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP + # scenario tests for new factories + chain.snapshot() + create_and_enact_add_recipient_motion( + easy_track, + STONKS_MS, + stonks_steth_allowed_recipients_registry, + STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, + stranger, + "New recipient", + ldo_holder, + ) + create_and_enact_payment_motion( + easy_track, + STONKS_MS, + interface.AllowedRecipientRegistry(STONKS_STETH_TOPUP_FACTORY), + interface.Lido(STETH_TOKEN), + [stranger], + [10 * 10**18], + stranger, + ) + create_and_enact_remove_recipient_motion( + easy_track, + STONKS_MS, + stonks_steth_allowed_recipients_registry, + STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, + stranger, + ldo_holder, + ) + + create_and_enact_add_recipient_motion( + easy_track, + STONKS_MS, + stonks_stablecoins_allowed_recipients_registry, + STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, + stranger, + "New recipient", + ldo_holder, + ) + create_and_enact_payment_motion( + easy_track, + STONKS_MS, + interface.AllowedRecipientRegistry(STONKS_STABLECOINS_TOPUP_FACTORY), + interface.ERC20(USDC_TOKEN), + [stranger], + [10 * 10**6], + stranger, + ) + create_and_enact_remove_recipient_motion( + easy_track, + STONKS_MS, + stonks_stablecoins_allowed_recipients_registry, + STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, + stranger, + ldo_holder, + ) + chain.revert() + # scenraio test for TRP ET factory behavior after the vote trp_limit_test(stranger) From 2385aa726e5aab9b76f569d0d28a1598c794abde Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 29 Nov 2025 10:23:10 +0000 Subject: [PATCH 064/178] feat: add stonks perms tests --- scripts/vote_2025_12_10.py | 10 +++-- tests/test_2025_12_10.py | 75 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index b92d9aeca..8bd82fbe2 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -86,6 +86,8 @@ class TokenLimit(NamedTuple): # ============================== Roles =================================== CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" +ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE = "ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE" +REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE = "REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE" # ============================== Tokens =================================== @@ -270,7 +272,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: # 1.5 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 ( stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text="ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE")), + convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR, ) ), @@ -279,7 +281,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: # 1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 ( stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text="ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE")), + convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR, ) ), @@ -288,7 +290,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: # 1.7 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 ( stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text="REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE")), + convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR, ) ), @@ -297,7 +299,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: # 1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 ( stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text="REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE")), + convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR, ) ), diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 343cf2118..2bed6587f 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -113,6 +113,8 @@ class TokenLimit(NamedTuple): CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" +ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE = "ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE" +REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE = "REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE" # ============================== Constants =================================== @@ -406,7 +408,7 @@ def dual_governance_proposal_calls(): agent_forward([ ( stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text="ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE")), + convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR, ) ), @@ -414,7 +416,7 @@ def dual_governance_proposal_calls(): agent_forward([ ( stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text="ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE")), + convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR, ) ), @@ -422,7 +424,7 @@ def dual_governance_proposal_calls(): agent_forward([ ( stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text="REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE")), + convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR, ) ), @@ -430,7 +432,7 @@ def dual_governance_proposal_calls(): agent_forward([ ( stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text="REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE")), + convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR, ) ), @@ -909,6 +911,24 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP + # Items 1.5-1.8 + assert not stonks_steth_allowed_recipients_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), + ET_EVM_SCRIPT_EXECUTOR + ) + assert not stonks_stablecoins_allowed_recipients_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), + ET_EVM_SCRIPT_EXECUTOR + ) + assert not stonks_steth_allowed_recipients_registry.hasRole( + convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), + ET_EVM_SCRIPT_EXECUTOR + ) + assert not stonks_stablecoins_allowed_recipients_registry.hasRole( + convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), + ET_EVM_SCRIPT_EXECUTOR + ) + if details["status"] == PROPOSAL_STATUS["submitted"]: chain.sleep(timelock.getAfterSubmitDelay() + 1) @@ -963,6 +983,34 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, emitted_by=ET_TRP_REGISTRY, ) + validate_grant_role_event( + events=dg_events[4], + role=web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE).hex(), + grant_to=ET_EVM_SCRIPT_EXECUTOR, + sender=AGENT, + emitted_by=STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY, + ) + validate_grant_role_event( + events=dg_events[5], + role=web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE).hex(), + grant_to=ET_EVM_SCRIPT_EXECUTOR, + sender=AGENT, + emitted_by=STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY, + ) + validate_grant_role_event( + events=dg_events[6], + role=web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE).hex(), + grant_to=ET_EVM_SCRIPT_EXECUTOR, + sender=AGENT, + emitted_by=STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY, + ) + validate_grant_role_event( + events=dg_events[7], + role=web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE).hex(), + grant_to=ET_EVM_SCRIPT_EXECUTOR, + sender=AGENT, + emitted_by=STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY, + ) # ========================================================================= # ==================== After DG proposal executed checks ================== @@ -1029,6 +1077,25 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP + # Items 1.5-1.8 + assert stonks_steth_allowed_recipients_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), + ET_EVM_SCRIPT_EXECUTOR + ) + assert stonks_stablecoins_allowed_recipients_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), + ET_EVM_SCRIPT_EXECUTOR + ) + assert stonks_steth_allowed_recipients_registry.hasRole( + convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), + ET_EVM_SCRIPT_EXECUTOR + ) + assert stonks_stablecoins_allowed_recipients_registry.hasRole( + convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), + ET_EVM_SCRIPT_EXECUTOR + ) + + # scenario tests for new factories chain.snapshot() create_and_enact_add_recipient_motion( From a183e3d190c8a767c06b8ddcf5c93a4e33fe48ab Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sun, 30 Nov 2025 09:26:44 +0100 Subject: [PATCH 065/178] Update stake share limit terminology from 'bps' to 'BP' --- scripts/vote_2025_12_10.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 8bd82fbe2..5d34b545b 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -6,7 +6,7 @@ 1.1. Change Curated Module (MODULE_ID = 1) module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 II. Raise SDVT stake share limit -1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 III. Reset Easy Track TRP limit 1.3. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO @@ -242,7 +242,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateStakingModule.encode_input( From 4ed1f05f5bddf0d1709ff42f99c1f0e14620f5a1 Mon Sep 17 00:00:00 2001 From: dry914 Date: Sun, 30 Nov 2025 18:10:36 +0300 Subject: [PATCH 066/178] tests: add 101225 test case --- interfaces/IVaultsAdapter.json | 112 ++ interfaces/UpgradeTemplateV3.json | 1396 ++++++++++++++++++++++ scripts/upgrade_2025_12_10_mainnet_v3.py | 19 +- tests/test_2025_12_10_mainnet_v3.py | 645 +++++++++- 4 files changed, 2138 insertions(+), 34 deletions(-) create mode 100644 interfaces/IVaultsAdapter.json create mode 100644 interfaces/UpgradeTemplateV3.json diff --git a/interfaces/IVaultsAdapter.json b/interfaces/IVaultsAdapter.json new file mode 100644 index 000000000..661ae1a56 --- /dev/null +++ b/interfaces/IVaultsAdapter.json @@ -0,0 +1,112 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_pubkeys", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_feeRecipient", + "type": "address" + } + ], + "name": "forceValidatorExit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_liabilitySharesTarget", + "type": "uint256" + } + ], + "name": "setLiabilitySharesTarget", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isInJail", + "type": "bool" + } + ], + "name": "setVaultJailStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_debtVault", + "type": "address" + }, + { + "internalType": "address", + "name": "_acceptorVault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shares", + "type": "uint256" + } + ], + "name": "socializeBadDebt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint16", + "name": "_infrastructureFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "_liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "_reservationFeeBP", + "type": "uint16" + } + ], + "name": "updateVaultFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/interfaces/UpgradeTemplateV3.json b/interfaces/UpgradeTemplateV3.json new file mode 100644 index 000000000..5c0209da1 --- /dev/null +++ b/interfaces/UpgradeTemplateV3.json @@ -0,0 +1,1396 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "oldLocatorImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "oldLidoImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "oldAccountingOracleImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "oldTokenRateNotifier", + "type": "address" + }, + { + "internalType": "address", + "name": "newLocatorImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "newLidoImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "newAccountingOracleImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "newTokenRateNotifier", + "type": "address" + }, + { + "internalType": "address", + "name": "upgradeableBeacon", + "type": "address" + }, + { + "internalType": "address", + "name": "stakingVaultImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "dashboardImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "gateSealForVaults", + "type": "address" + }, + { + "internalType": "address", + "name": "kernel", + "type": "address" + }, + { + "internalType": "address", + "name": "agent", + "type": "address" + }, + { + "internalType": "address", + "name": "aragonAppLidoRepo", + "type": "address" + }, + { + "internalType": "address", + "name": "locator", + "type": "address" + }, + { + "internalType": "address", + "name": "voting", + "type": "address" + }, + { + "internalType": "address", + "name": "dualGovernance", + "type": "address" + }, + { + "internalType": "address", + "name": "acl", + "type": "address" + }, + { + "internalType": "address", + "name": "resealManager", + "type": "address" + }, + { + "internalType": "address", + "name": "easyTrack", + "type": "address" + }, + { + "internalType": "address", + "name": "vaultsAdapter", + "type": "address" + }, + { + "internalType": "address", + "name": "etfAlterTiersInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfRegisterGroupsInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfRegisterTiersInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfUpdateGroupsShareLimitInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfSetJailStatusInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfUpdateVaultsFeesInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfForceValidatorExitsInVaultHub", + "type": "address" + }, + { + "internalType": "address", + "name": "etfSetLiabilitySharesTargetInVaultHub", + "type": "address" + }, + { + "internalType": "address", + "name": "etfSocializeBadDebtInVaultHub", + "type": "address" + } + ], + "internalType": "struct V3Addresses.V3AddressesParams", + "name": "_params", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_expireSinceInclusive", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_initialMaxExternalRatioBP", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BurnerMigrationNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "Expired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "repo", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "IncorrectAragonAppImplementation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "burner", + "type": "address" + } + ], + "name": "IncorrectBurnerAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "IncorrectBurnerSharesMigration", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "IncorrectOZAccessControlRoleHolders", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "IncorrectProxyAdmin", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "IncorrectProxyImplementation", + "type": "error" + }, + { + "inputs": [], + "name": "IncorrectTokenRateNotifierObserversLengthMigration", + "type": "error" + }, + { + "inputs": [], + "name": "IncorrectTokenRateNotifierObserversMigration", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "notifier", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "IncorrectTokenRateNotifierOwnerMigration", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beacon", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "IncorrectUpgradeableBeaconImplementation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beacon", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "IncorrectUpgradeableBeaconOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "IncorrectVaultFactoryBeacon", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "delegation", + "type": "address" + } + ], + "name": "IncorrectVaultFactoryDashboardImplementation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualVersion", + "type": "uint256" + } + ], + "name": "InvalidContractVersion", + "type": "error" + }, + { + "inputs": [], + "name": "NewAndOldLocatorImplementationsMustBeDifferent", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "NonZeroRoleHolders", + "type": "error" + }, + { + "inputs": [], + "name": "OldAndNewTokenRateNotifiersMustBeDifferent", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyAgentCanUpgrade", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "moduleName", + "type": "string" + } + ], + "name": "StakingModuleNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "StartAlreadyCalledInThisTx", + "type": "error" + }, + { + "inputs": [], + "name": "StartAndFinishMustBeInSameTx", + "type": "error" + }, + { + "inputs": [], + "name": "TotalSharesOrPooledEtherChanged", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedEasyTrackFactories", + "type": "error" + }, + { + "inputs": [], + "name": "UpgradeAlreadyFinished", + "type": "error" + }, + { + "inputs": [], + "name": "UpgradeAlreadyStarted", + "type": "error" + }, + { + "anonymous": false, + "inputs": [], + "name": "UpgradeFinished", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "UpgradeStarted", + "type": "event" + }, + { + "inputs": [], + "name": "ACCOUNTING", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACCOUNTING_ORACLE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACL", + "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": "ARAGON_APP_LIDO_REPO", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BURNER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CSM_ACCOUNTING", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CSM_MODULE_NAME", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CURATED_MODULE_NAME", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DASHBOARD_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DUAL_GOVERNANCE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EASY_TRACK", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EL_REWARDS_VAULT", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_ALTER_TIERS_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_FORCE_VALIDATOR_EXITS_IN_VAULT_HUB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_REGISTER_GROUPS_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_REGISTER_TIERS_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_SET_JAIL_STATUS_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_SOCIALIZE_BAD_DEBT_IN_VAULT_HUB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_UPDATE_VAULTS_FEES_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EVM_SCRIPT_EXECUTOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPECTED_FINAL_ACCOUNTING_ORACLE_CONSENSUS_VERSION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPECTED_FINAL_ACCOUNTING_ORACLE_VERSION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPECTED_FINAL_LIDO_VERSION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPIRE_SINCE_INCLUSIVE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GATE_SEAL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INFINITE_ALLOWANCE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INITIAL_MAX_EXTERNAL_RATIO_BP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KERNEL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LAZY_ORACLE", + "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": "LOCATOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NEW_ACCOUNTING_ORACLE_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NEW_LIDO_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NEW_LOCATOR_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NEW_TOKEN_RATE_NOTIFIER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NODE_OPERATORS_REGISTRY", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_ACCOUNTING_ORACLE_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_BURNER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_LIDO_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_LOCATOR_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_TOKEN_RATE_NOTIFIER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ORACLE_DAEMON_CONFIG", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ORACLE_REPORT_SANITY_CHECKER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PREDEPOSIT_GUARANTEE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESEAL_MANAGER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SIMPLE_DVT", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SIMPLE_DVT_MODULE_NAME", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "STAKING_ROUTER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "STAKING_VAULT_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADEABLE_BEACON", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADE_NOT_STARTED", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADE_STARTED_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VALIDATORS_EXIT_BUS_ORACLE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VAULTS_ADAPTER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VAULT_FACTORY", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VAULT_HUB", + "outputs": [ + { + "internalType": "address payable", + "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": [], + "name": "WSTETH", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "contractsWithBurnerAllowances", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "finishUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialOldBurnerStethSharesBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialTotalPooledEther", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialTotalShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isUpgradeFinished", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "upgradeBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index c2294166f..b06d41e26 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -2,7 +2,24 @@ # Vote 2025_12_10 === 1. DG PROPOPSAL === -1. Lido V3 upgrade - stVaults +1.1. Check DG voting enactment is within daily time window (14:00 UTC - 23:00 UTC) +1.2. Call UpgradeTemplateV3.startUpgrade +1.3. Upgrade LidoLocator implementation +1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT +1.5. Set Lido implementation in Kernel +1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT +1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido +1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module +1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT +1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting +1.11. Upgrade AccountingOracle implementation +1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido +1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting +1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent +1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig +1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig +1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent +1.18. Call UpgradeTemplateV3.finishUpgrade === NON-DG ITEMS === 2. Add AlterTiersInOperatorGrid factory to EasyTrack (permissions: operatorGrid, alterTiers); diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index 6883ac473..e6c973955 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -1,4 +1,5 @@ -from brownie import chain, interface +from typing import Optional +from brownie import chain, interface, web3, convert from brownie.network.transaction import TransactionReceipt import pytest @@ -15,6 +16,13 @@ from utils.dual_governance import PROPOSAL_STATUS, wait_for_target_time_to_satisfy_time_constrains from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event +from utils.agent import agent_forward +from utils.permissions import encode_oz_grant_role, encode_oz_revoke_role +from utils.test.event_validators.easy_track import validate_evmscript_factory_added_event, EVMScriptFactoryAdded +from utils.easy_track import create_permissions +from brownie.network.event import EventDict +from utils.test.event_validators.common import validate_events_chain + # ============================================================================ # ============================== Import vote ================================= @@ -25,36 +33,306 @@ # ============================================================================ # ============================== Constants =================================== # ============================================================================ -# TODO list all contract addresses used in tests - do not use imports from config! -# NOTE: these addresses might have a different value on other chains +DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000" + +# Voting addresses VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" +DUAL_GOVERNANCE_TIME_CONSTRAINTS = "0x2a30F5aC03187674553024296bed35Aa49749DDa" +ARAGON_KERNEL = "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc" +ACL = "0x9895F0F17cc1d1891b6f18ee0b483B6f221b37Bb" +EASYTRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" +EASYTRACK_EVMSCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" + +# Old Lido addresses +STETH = "0xAE7ab96520DE3A18E5e111B5EaAb095312D7fE84" +LIDO_LOCATOR = "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" +ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +ACL = "0x9895F0F17cc1d1891b6f18ee0b483B6f221b37Bb" +NODE_OPERATORS_REGISTRY = "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" +SIMPLE_DVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" +ORACLE_DAEMON_CONFIG = "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09" +CSM_ACCOUNTING = "0x4d72BFF1BeaC69925F8Bd12526a39BAAb069e5Da" +OLD_BURNER = "0xD15a672319Cf0352560eE76d9e89eAB0889046D3" +LIDO_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" + +# New Lido V3 addresses - TODO fix after mainnet deployment +VAULT_HUB = "0xdcC04F506E24495E9F2599A7b214522647363669" +ACCOUNTING = "0xc9158c756D2a510eaC85792C847798a30dE20D46" +ACCOUNTING_IMPL = "0x1bE2Ee7D32e3F9C4ecd5b6BfF69306ACb8Ab239e" +OPERATOR_GRID = "0x79e2685C1DD4756AC709a6CeE7C6cC960128B031" +LAZY_ORACLE = "0x4fA3c917BB8f8CD9d056C5DDF0a38bd1834c43F9" +PREDEPOSIT_GUARANTEE = "0x7B49b203A100E326B84886dCC0e2c426f9b8cbBd" +VAULTS_ADAPTER = "0x8cDA09f41970A8f4416b6bA4696a2f09a6080c76" +GATE_SEAL_V3 = "0x9c2D30177DB12334998EB554f5d4E6dD44458167" +LIDO_IMPL = "0xD0b9826e0EAf6dE91CE7A6783Cd6fd137ae422Ec" +UPGRADE_TEMPLATE = "0x2AE2847a77a1d24100be2163AfdC49B06DC808E4" +LIDO_LOCATOR_IMPL = "0x26329a3D4cF2F89923b5c4C14a25A2485cD01aA2" +ACCOUNTING_ORACLE_IMPL = "0xb2295820F5286BE40c2da8102eB2cDB24aD608Be" +RESEAL_MANAGER = "0x7914b5a1539b97Bd0bbd155757F25FD79A522d24" +BURNER = "0xD140f4f3C515E1a328F6804C5426d9e8b883ED50" + +ALTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0x25AB9D07356E8a3F95a5905f597c93CD8F31990b" +REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY = "0x9Ba3E4aDDe415943A831b97F9f7B8b842052b709" +REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0x663cE8Aa1ded537Dc319529e71DE6BAb2E7D0747" +SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY = "0x88D32aABa5B6A972D82E55D26B212eBeca07C983" +SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY = "0xf61601787E84d89c30911e5A48d9CA630eC47044" +SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY = "0xD60671a489948641954736A0e7272137b3A335CE" +FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY = "0x6D0F542591eF4f8ebA8c77a11dc3676D9E9F7e66" +UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY = "0xAEDB8D15197984D39152A2814e0BdCDAEED5462d" +UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY = "0x7DCf0746ff2F77A36ceC8E318b59dc8a8A5c066e" + +UTC14 = 60 * 60 * 14 +UTC23 = 60 * 60 * 23 +SLASHING_RESERVE_SHIFT = 8192 -# TODO Set variable to None if item is not presented EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 EXPECTED_VOTE_EVENTS_COUNT = 10 +EXPECTED_DG_EVENTS_FROM_AGENT = 17 EXPECTED_DG_EVENTS_COUNT = 18 -EXPECTED_DG_AGENT_EVENTS_COUNT = 17 # Events from Agent (excluding first DG submission event) IPFS_DESCRIPTION_HASH = "bafkreic4xuaowfowt7faxnngnzynv7biuo7guv4s4jrngngjzzxyz3up2i" +# ============================================================================ +# ============================== Helper functions ============================ +# ============================================================================ + +def validate_proxy_upgrade_event(event: EventDict, implementation: str, emitted_by: Optional[str] = None, events_chain: Optional[list] = None): + _events_chain = events_chain or ["LogScriptCall", "Upgraded", "ScriptResult", "Executed"] + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1 + assert event.count("Upgraded") == 1 + + assert "Upgraded" in event, "No Upgraded event found" + + assert event["Upgraded"][0]["implementation"] == implementation, "Wrong implementation address" + + if emitted_by is not None: + assert convert.to_address(event["Upgraded"][0]["_emitted_by"]) == convert.to_address( + emitted_by), "Wrong event emitter" + + +def validate_role_grant_event(event: EventDict, role_hash: str, account: str, emitted_by: Optional[str] = None): + _events_chain = ["LogScriptCall", "RoleGranted", "ScriptResult", "Executed"] + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1 + assert event.count("RoleGranted") == 1 + + assert "RoleGranted" in event, "No RoleGranted event found" + + # Strip 0x prefix for consistent comparison + expected_role_hash = role_hash.replace('0x', '') + actual_role_hash = event["RoleGranted"][0]["role"].hex().replace('0x', '') + + assert actual_role_hash == expected_role_hash, "Wrong role hash" + + assert convert.to_address(event["RoleGranted"][0]["account"]) == convert.to_address(account), "Wrong account" + + if emitted_by is not None: + assert convert.to_address(event["RoleGranted"][0]["_emitted_by"]) == convert.to_address( + emitted_by), "Wrong event emitter" + + +def validate_role_revoke_event(event: EventDict, role_hash: str, account: str, emitted_by: Optional[str] = None): + _events_chain = ["LogScriptCall", "RoleRevoked", "ScriptResult", "Executed"] + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1 + assert event.count("RoleRevoked") == 1 + + assert "RoleRevoked" in event, "No RoleRevoked event found" + + # Strip 0x prefix for consistent comparison + expected_role_hash = role_hash.replace('0x', '') + actual_role_hash = event["RoleRevoked"][0]["role"].hex().replace('0x', '') + + assert actual_role_hash == expected_role_hash, "Wrong role hash" + + assert convert.to_address(event["RoleRevoked"][0]["account"]) == convert.to_address(account), "Wrong account" + + if emitted_by is not None: + assert convert.to_address(event["RoleRevoked"][0]["_emitted_by"]) == convert.to_address( + emitted_by), "Wrong event emitter" + + +def get_ossifiable_proxy_impl(proxy_address): + """Get implementation address from an OssifiableProxy""" + proxy = interface.OssifiableProxy(proxy_address) + return proxy.proxy__getImplementation() + + +# ============================================================================ +# ============================== Test functions ============================== +# ============================================================================ + @pytest.fixture(scope="module") def dual_governance_proposal_calls(): - # TODO Create all the dual governance calls that match the voting script + """Returns list of dual governance proposal calls for events checking""" + + # Helper function to encode proxy upgrades + def encode_proxy_upgrade_to(proxy_contract, new_impl_address): + return (proxy_contract.address, proxy_contract.proxy__upgradeTo.encode_input(new_impl_address)) + + lido_locator_proxy = interface.OssifiableProxy(LIDO_LOCATOR) + upgradeTemplate = interface.UpgradeTemplateV3(UPGRADE_TEMPLATE) + old_burner = interface.Burner(OLD_BURNER) + oracle_daemon_config = interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) + accounting_oracle_proxy = interface.OssifiableProxy(ACCOUNTING_ORACLE) + kernel = interface.Kernel(ARAGON_KERNEL) + acl = interface.ACL(ACL) + staking_router = interface.StakingRouter(STAKING_ROUTER) + dg_items = [ - # # TODO 1.1. DG voting item 1 description - # agent_forward([ - # (dg_item_address_1, dg_item_encoded_input_1) - # ]), - # # TODO 1.2. DG voting item 2 description - # agent_forward([ - # (dg_item_address_2, dg_item_encoded_input_2) - # ]), + # 1.1. Check DG voting enactment is within daily time window (14:00 UTC - 23:00 UTC) + ( + DUAL_GOVERNANCE_TIME_CONSTRAINTS, + interface.TimeConstraints(DUAL_GOVERNANCE_TIME_CONSTRAINTS).checkTimeWithinDayTimeAndEmit.encode_input( + UTC14, # 14:00 UTC + UTC23 # 23:00 UTC + ), + ), + + # 1.2. Call UpgradeTemplateV3.startUpgrade + agent_forward([ + (upgradeTemplate.address, upgradeTemplate.startUpgrade.encode_input()) + ]), + + # 1.3. Upgrade LidoLocator implementation + agent_forward([encode_proxy_upgrade_to(lido_locator_proxy, LIDO_LOCATOR_IMPL)]), + + # 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT + agent_forward([ + (acl.address, + acl.grantPermission.encode_input( + AGENT, + ARAGON_KERNEL, + web3.keccak(text="APP_MANAGER_ROLE") + )) + ]), + + # 1.5. Set Lido implementation in Kernel + agent_forward([ + (kernel.address, + kernel.setApp.encode_input( + kernel.APP_BASES_NAMESPACE(), + LIDO_APP_ID, + LIDO_IMPL + )) + ]), + + # 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT + agent_forward([ + (acl.address, + acl.revokePermission.encode_input( + AGENT, + ARAGON_KERNEL, + web3.keccak(text="APP_MANAGER_ROLE") + )) + ]), + + # 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + agent_forward([ + encode_oz_revoke_role( + contract=old_burner, + role_name="REQUEST_BURN_SHARES_ROLE", + revoke_from=STETH # Lido + ) + ]), + + # 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + agent_forward([ + encode_oz_revoke_role( + contract=old_burner, + role_name="REQUEST_BURN_SHARES_ROLE", + revoke_from=NODE_OPERATORS_REGISTRY + ) + ]), + + # 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + agent_forward([ + encode_oz_revoke_role( + contract=old_burner, + role_name="REQUEST_BURN_SHARES_ROLE", + revoke_from=SIMPLE_DVT + ) + ]), + + # 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + agent_forward([ + encode_oz_revoke_role( + contract=old_burner, + role_name="REQUEST_BURN_SHARES_ROLE", + revoke_from=CSM_ACCOUNTING + ) + ]), + + # 1.11. Upgrade AccountingOracle implementation + agent_forward([encode_proxy_upgrade_to(accounting_oracle_proxy, ACCOUNTING_ORACLE_IMPL)]), + + # 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido + agent_forward([ + encode_oz_revoke_role( + contract=staking_router, + role_name="REPORT_REWARDS_MINTED_ROLE", + revoke_from=STETH # Lido + ) + ]), + + # 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting + agent_forward([ + encode_oz_grant_role( + contract=staking_router, + role_name="REPORT_REWARDS_MINTED_ROLE", + grant_to=ACCOUNTING + ) + ]), + + # 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent + agent_forward([ + encode_oz_grant_role( + contract=oracle_daemon_config, + role_name="CONFIG_MANAGER_ROLE", + grant_to=AGENT + ) + ]), + + # 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + agent_forward([ + (oracle_daemon_config.address, oracle_daemon_config.set.encode_input( + "SLASHING_RESERVE_WE_RIGHT_SHIFT", + web3.codec.encode(['uint256'], [SLASHING_RESERVE_SHIFT]) + )) + ]), + + # 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + agent_forward([ + (oracle_daemon_config.address, oracle_daemon_config.set.encode_input( + "SLASHING_RESERVE_WE_LEFT_SHIFT", + web3.codec.encode(['uint256'], [SLASHING_RESERVE_SHIFT]) + )) + ]), + + # 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent + agent_forward([ + encode_oz_revoke_role( + contract=oracle_daemon_config, + role_name="CONFIG_MANAGER_ROLE", + revoke_from=AGENT + ) + ]), + + # 1.18. Call UpgradeTemplateV3.finishUpgrade + agent_forward([ + (upgradeTemplate.address, upgradeTemplate.finishUpgrade.encode_input()) + ]), ] # Convert each dg_item to the expected format @@ -79,7 +357,22 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g agent = interface.Agent(AGENT) timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + easy_track = interface.EasyTrack(EASYTRACK) + + vault_hub = interface.VaultHub(VAULT_HUB) + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + predeposit_guarantee = interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) + burner = interface.Burner(BURNER) + lido_locator_proxy = interface.OssifiableProxy(LIDO_LOCATOR) + accounting_oracle_proxy = interface.OssifiableProxy(ACCOUNTING_ORACLE) + staking_router = interface.StakingRouter(STAKING_ROUTER) + old_burner = interface.Burner(OLD_BURNER) + oracle_daemon_config = interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) + + # Save original implementations for comparison + locator_impl_before = get_ossifiable_proxy_impl(LIDO_LOCATOR) + accounting_oracle_impl_before = get_ossifiable_proxy_impl(ACCOUNTING_ORACLE) # ========================================================================= # ======================== Identify or Create vote ======================== @@ -106,49 +399,223 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # ========================= Before voting checks ======================== # ======================================================================= - # TODO add before voting checks + # Steps 2-10: Add EasyTrack factories + initial_factories = easy_track.getEVMScriptFactories() + assert ALTER_TIERS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have ALTER_TIERS_IN_OPERATOR_GRID_FACTORY factory before vote" + assert REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY factory before vote" + assert REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY factory before vote" + assert SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY factory before vote" + assert SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY not in initial_factories, "EasyTrack should not have SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY factory before vote" + assert SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY not in initial_factories, "EasyTrack should not have SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY factory before vote" + assert FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY not in initial_factories, "EasyTrack should not have FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY factory before vote" + assert UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY factory before vote" + assert UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY factory before vote" assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) display_voting_events(vote_tx) - vote_events = group_voting_events_from_receipt(vote_tx) # ======================================================================= # ========================= After voting checks ========================= # ======================================================================= - # TODO add after voting tests + # Deployment state checks: + # Check Predeposit Guarantee roles + pause_role = web3.keccak(text="PausableUntilWithRoles.PauseRole") + resume_role = web3.keccak(text="PausableUntilWithRoles.ResumeRole") + assert predeposit_guarantee.hasRole(pause_role, GATE_SEAL_V3), "Predeposit Guarantee should have PAUSE_ROLE on GATE_SEAL_V3 after upgrade" + assert predeposit_guarantee.hasRole(pause_role, RESEAL_MANAGER), "Predeposit Guarantee should have PAUSE_ROLE on RESEAL_MANAGER after upgrade" + assert predeposit_guarantee.hasRole(resume_role, RESEAL_MANAGER), "Predeposit Guarantee should have RESUME_ROLE on RESEAL_MANAGER after upgrade" + assert predeposit_guarantee.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Predeposit Guarantee should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" + # Check Operator Grid roles + registry_role = web3.keccak(text="vaults.OperatorsGrid.Registry") + assert operator_grid.hasRole(registry_role, VAULTS_ADAPTER), "Operator Grid should have REGISTRY_ROLE on VAULTS_ADAPTER after upgrade" + assert operator_grid.hasRole(registry_role, EASYTRACK_EVMSCRIPT_EXECUTOR), "Operator Grid should have REGISTRY_ROLE on EASYTRACK_EVMSCRIPT_EXECUTOR after upgrade" + assert operator_grid.hasRole(registry_role, AGENT), "Operator Grid should have REGISTRY_ROLE on AGENT after upgrade" + assert operator_grid.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Operator Grid should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" + # Check Burner roles + request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") + assert burner.hasRole(request_burn_shares_role, ACCOUNTING), "Burner should have REQUEST_BURN_SHARES_ROLE on ACCOUNTING after upgrade" + assert burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Burner should have REQUEST_BURN_SHARES_ROLE on CSM_ACCOUNTING after upgrade" + assert burner.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Burner should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" + # Check Vault Hub roles + assert vault_hub.hasRole(pause_role, GATE_SEAL_V3), "Vault Hub should have PAUSE_ROLE on GATE_SEAL_V3 after upgrade" + assert vault_hub.hasRole(pause_role, RESEAL_MANAGER), "Vault Hub should have PAUSE_ROLE on RESEAL_MANAGER after upgrade" + assert vault_hub.hasRole(resume_role, RESEAL_MANAGER), "Vault Hub should have RESUME_ROLE on RESEAL_MANAGER after upgrade" + vault_master_role = web3.keccak(text="vaults.VaultHub.VaultMasterRole") + assert vault_hub.hasRole(vault_master_role, AGENT), "Vault Hub should have VAULT_MASTER_ROLE on AGENT after upgrade" + validator_exit_role = web3.keccak(text="vaults.VaultHub.ValidatorExitRole") + assert vault_hub.hasRole(validator_exit_role, VAULTS_ADAPTER), "Vault Hub should have VALIDATOR_EXIT_ROLE on VAULTS_ADAPTER after upgrade" + bad_debt_master_role = web3.keccak(text="vaults.VaultHub.BadDebtMasterRole") + assert vault_hub.hasRole(bad_debt_master_role, VAULTS_ADAPTER), "Vault Hub should have BAD_DEBT_MASTER_ROLE on VAULTS_ADAPTER after upgrade" + assert vault_hub.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Vault Hub` should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" + + # Steps 2-10: Add EasyTrack factories + new_factories = easy_track.getEVMScriptFactories() + assert ALTER_TIERS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have ALTER_TIERS_IN_OPERATOR_GRID_FACTORY factory after vote" + assert REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY factory after vote" + assert REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY factory after vote" + assert SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY factory after vote" + assert SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY in new_factories, "EasyTrack should have SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY factory after vote" + assert SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY in new_factories, "EasyTrack should have SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY factory after vote" + assert FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY in new_factories, "EasyTrack should have FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY factory after vote" + assert UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY factory after vote" + assert UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY factory after vote" + vote_events = group_voting_events_from_receipt(vote_tx) assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT + if EXPECTED_DG_PROPOSAL_ID is not None: assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - # TODO Validate DG Proposal Submit event - # validate_dual_governance_submit_event( - # vote_events[0], - # proposal_id=EXPECTED_DG_PROPOSAL_ID, - # proposer=VOTING, - # executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - # metadata="TODO DG proposal description", - # proposal_calls=dual_governance_proposal_calls, - # emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], - # ) - - # TODO validate all other voting events + validate_dual_governance_submit_event( + vote_events[0], + proposal_id=EXPECTED_DG_PROPOSAL_ID, + proposer=VOTING, + executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + metadata="TODO DG proposal description", + proposal_calls=dual_governance_proposal_calls, + emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], + ) + + # Validate EasyTrack bypass events for new factories + validate_evmscript_factory_added_event( + event=vote_events[1], + p=EVMScriptFactoryAdded( + factory_addr=ALTER_TIERS_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(interface.OperatorGrid(OPERATOR_GRID), "alterTiers") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[2], + p=EVMScriptFactoryAdded( + factory_addr=REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(interface.OperatorGrid(OPERATOR_GRID), "registerGroup") + create_permissions(interface.OperatorGrid(OPERATOR_GRID), "registerTiers")[2:] + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[3], + p=EVMScriptFactoryAdded( + factory_addr=REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(interface.OperatorGrid(OPERATOR_GRID), "registerTiers") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[4], + p=EVMScriptFactoryAdded( + factory_addr=UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(interface.OperatorGrid(OPERATOR_GRID), "updateGroupShareLimit") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[5], + p=EVMScriptFactoryAdded( + factory_addr=SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "setVaultJailStatus") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[6], + p=EVMScriptFactoryAdded( + factory_addr=UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "updateVaultFees") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[7], + p=EVMScriptFactoryAdded( + factory_addr=FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY, + permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "forceValidatorExit") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[8], + p=EVMScriptFactoryAdded( + factory_addr=SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY, + permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "setLiabilitySharesTarget") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[9], + p=EVMScriptFactoryAdded( + factory_addr=SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY, + permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "socializeBadDebt") + ), + emitted_by=easy_track, + ) if EXPECTED_DG_PROPOSAL_ID is not None: + report_rewards_minted_role = web3.keccak(text="REPORT_REWARDS_MINTED_ROLE") + request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") + config_manager_role = web3.keccak(text="CONFIG_MANAGER_ROLE") + details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) if details["status"] != PROPOSAL_STATUS["executed"]: # ========================================================================= # ================== DG before proposal executed checks =================== # ========================================================================= - # TODO add DG before proposal executed checks + # Step 1.3: Check Lido Locator implementation initial state + assert locator_impl_before != LIDO_LOCATOR_IMPL, "Locator implementation should be different before upgrade" + + # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + assert old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Lido before upgrade" + + # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + assert old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Curated staking module before upgrade" + + # Step 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + assert old_burner.hasRole(request_burn_shares_role, SIMPLE_DVT), "Old Burner should have REQUEST_BURN_SHARES_ROLE on SimpleDVT before upgrade" + + # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + assert old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting before upgrade" + + # Step 1.11: Check Accounting Oracle implementation initial state + assert accounting_oracle_impl_before != ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be different before upgrade" + + # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido + assert staking_router.hasRole(report_rewards_minted_role, STETH), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Lido before upgrade" + + # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting + assert not staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Accounting before upgrade" + + # Step 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent + assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent before upgrade" + + # Step 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + try: + oracle_daemon_config.get('SLASHING_RESERVE_WE_RIGHT_SHIFT') + assert False, "SLASHING_RESERVE_WE_RIGHT_SHIFT should not exist before vote" + except Exception: + pass # Expected to fail + + # Step 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + try: + oracle_daemon_config.get('SLASHING_RESERVE_WE_LEFT_SHIFT') + assert False, "SLASHING_RESERVE_WE_LEFT_SHIFT should not exist before vote" + except Exception: + pass # Expected to fail if details["status"] == PROPOSAL_STATUS["submitted"]: chain.sleep(timelock.getAfterSubmitDelay() + 1) @@ -166,13 +633,125 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g timelock=EMERGENCY_PROTECTED_TIMELOCK, admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, ) - assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_AGENT_EVENTS_COUNT + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_FROM_AGENT assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT - # TODO validate all DG events + # === DG EXECUTION EVENTS VALIDATION === + + # 1.3. Lido Locator upgrade events + validate_proxy_upgrade_event(dg_events[2], LIDO_LOCATOR_IMPL, emitted_by=lido_locator_proxy) + + # 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + validate_role_revoke_event( + dg_events[6], + role_hash=request_burn_shares_role.hex(), + account=STETH, + emitted_by=old_burner + ) + + # 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + validate_role_revoke_event( + dg_events[7], + role_hash=request_burn_shares_role.hex(), + account=NODE_OPERATORS_REGISTRY, + emitted_by=old_burner + ) + + # 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + validate_role_revoke_event( + dg_events[8], + role_hash=request_burn_shares_role.hex(), + account=SIMPLE_DVT, + emitted_by=old_burner + ) + + # 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + validate_role_revoke_event( + dg_events[9], + role_hash=request_burn_shares_role.hex(), + account=CSM_ACCOUNTING, + emitted_by=old_burner + ) + + # 1.11. Accounting Oracle upgrade events + validate_proxy_upgrade_event(dg_events[10], ACCOUNTING_ORACLE_IMPL, emitted_by=accounting_oracle_proxy) + + # 1.12. Revoke Staking Router REPORT_REWARDS_MINTED_ROLE from the Lido + validate_role_revoke_event( + dg_events[11], + role_hash=report_rewards_minted_role.hex(), + account=STETH, + emitted_by=staking_router + ) + + # 1.13. Grant Staking Router REPORT_REWARDS_MINTED_ROLE to Accounting + validate_role_grant_event( + dg_events[12], + role_hash=report_rewards_minted_role.hex(), + account=ACCOUNTING, + emitted_by=staking_router + ) + + # 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent + validate_role_grant_event( + dg_events[13], + role_hash=config_manager_role.hex(), + account=AGENT, + emitted_by=oracle_daemon_config + ) + + # 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + assert 'ConfigValueSet' in dg_events[14] + assert 'SLASHING_RESERVE_WE_RIGHT_SHIFT' in dg_events[14]['ConfigValueSet'][0]['key'] + assert convert.to_int(dg_events[14]['ConfigValueSet'][0]['value']) == SLASHING_RESERVE_SHIFT + + # 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + assert 'ConfigValueSet' in dg_events[15] + assert 'SLASHING_RESERVE_WE_LEFT_SHIFT' in dg_events[15]['ConfigValueSet'][0]['key'] + assert convert.to_int(dg_events[15]['ConfigValueSet'][0]['value']) == SLASHING_RESERVE_SHIFT + + # 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent + validate_role_revoke_event( + dg_events[16], + role_hash=config_manager_role.hex(), + account=AGENT, + emitted_by=oracle_daemon_config + ) # ========================================================================= # ==================== After DG proposal executed checks ================== # ========================================================================= - # TODO add DG after proposal executed checks + + # Step 1.3: Validate Lido Locator implementation was updated + assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" + + # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + assert not old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Lido after upgrade" + + # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + assert not old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Curated staking module after upgrade" + + # Step 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + assert not old_burner.hasRole(request_burn_shares_role, SIMPLE_DVT), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on SimpleDVT after upgrade" + + # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + assert not old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting after upgrade" + + # Step 1.11: Validate Accounting Oracle implementation was updated + assert get_ossifiable_proxy_impl(accounting_oracle_proxy) == ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be updated to the new value" + + # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido + assert not staking_router.hasRole(report_rewards_minted_role, STETH), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Lido after upgrade" + + # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting + assert staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Accounting after upgrade" + + # Step 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + assert convert.to_uint(oracle_daemon_config.get("SLASHING_RESERVE_WE_RIGHT_SHIFT")) == SLASHING_RESERVE_SHIFT, "OracleDaemonConfig should have SLASHING_RESERVE_WE_RIGHT_SHIFT set to 0x2000 after upgrade" + + # Step 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + assert convert.to_uint(oracle_daemon_config.get("SLASHING_RESERVE_WE_LEFT_SHIFT")) == SLASHING_RESERVE_SHIFT, "OracleDaemonConfig should have SLASHING_RESERVE_WE_LEFT_SHIFT set to 0x2000 after upgrade" + + # Step 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent + assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent after upgrade" From cf68a737d6aa953e5535e2d43b92ab8cbf607dbb Mon Sep 17 00:00:00 2001 From: dry914 Date: Sun, 30 Nov 2025 21:57:00 +0300 Subject: [PATCH 067/178] tests: add v3 test checks --- tests/test_2025_12_10_mainnet_v3.py | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index e6c973955..cf6911f60 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -358,6 +358,8 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) easy_track = interface.EasyTrack(EASYTRACK) + kernel = interface.Kernel(ARAGON_KERNEL) + acl = interface.ACL(ACL) vault_hub = interface.VaultHub(VAULT_HUB) operator_grid = interface.OperatorGrid(OPERATOR_GRID) @@ -569,6 +571,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g report_rewards_minted_role = web3.keccak(text="REPORT_REWARDS_MINTED_ROLE") request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") config_manager_role = web3.keccak(text="CONFIG_MANAGER_ROLE") + app_manager_role = web3.keccak(text="APP_MANAGER_ROLE") details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) if details["status"] != PROPOSAL_STATUS["executed"]: @@ -579,6 +582,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # Step 1.3: Check Lido Locator implementation initial state assert locator_impl_before != LIDO_LOCATOR_IMPL, "Locator implementation should be different before upgrade" + # Step 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT + assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE before upgrade" + + # Step 1.5. Set Lido implementation in Kernel + assert not kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be different before upgrade" + # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido assert old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Lido before upgrade" @@ -641,6 +650,26 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # 1.3. Lido Locator upgrade events validate_proxy_upgrade_event(dg_events[2], LIDO_LOCATOR_IMPL, emitted_by=lido_locator_proxy) + # 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT + assert 'SetPermission' in dg_events[3] + assert dg_events[3]['SetPermission'][0]['allowed'] is True + assert dg_events[3]['SetPermission'][0]['_emitted_by'] == ACL + assert dg_events[3]['SetPermission'][0]['entity'] == AGENT + assert dg_events[3]['SetPermission'][0]['role'] == app_manager_role.hex() + + # 1.5. Set Lido implementation in Kernel + assert 'SetApp' in dg_events[4] + assert dg_events[4]['SetApp'][0]['appId'] == LIDO_APP_ID + assert dg_events[4]['SetApp'][0]['_emitted_by'] == ARAGON_KERNEL + assert dg_events[4]['SetApp'][0]['app'] == LIDO_IMPL + + # 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT + assert 'SetPermission' in dg_events[5] + assert dg_events[5]['SetPermission'][0]['allowed'] is False + assert dg_events[5]['SetPermission'][0]['_emitted_by'] == ACL + assert dg_events[5]['SetPermission'][0]['entity'] == AGENT + assert dg_events[5]['SetPermission'][0]['role'] == app_manager_role.hex() + # 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido validate_role_revoke_event( dg_events[6], @@ -726,6 +755,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # Step 1.3: Validate Lido Locator implementation was updated assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" + # Step 1.5. Set Lido implementation in Kernel + assert kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be updated to the new value" + + # Step 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT + assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE after upgrade" + # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido assert not old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Lido after upgrade" From 989733201772a3a89cf250ea0cb73c7d70775a96 Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 1 Dec 2025 19:56:01 +0300 Subject: [PATCH 068/178] chore: DG items desc --- scripts/upgrade_2025_12_10_mainnet_v3.py | 28 ++++++++++++------------ tests/test_2025_12_10_mainnet_v3.py | 10 ++++----- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index b06d41e26..9be9a557b 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -2,8 +2,8 @@ # Vote 2025_12_10 === 1. DG PROPOPSAL === -1.1. Check DG voting enactment is within daily time window (14:00 UTC - 23:00 UTC) -1.2. Call UpgradeTemplateV3.startUpgrade +1.1. Ensure DG proposal execution is within daily time window (14:00 UTC - 23:00 UTC) +1.2. Call V3Template.startUpgrade 1.3. Upgrade LidoLocator implementation 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT 1.5. Set Lido implementation in Kernel @@ -16,21 +16,21 @@ 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent -1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig -1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig +1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT at OracleDaemonConfig +1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT at OracleDaemonConfig 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent -1.18. Call UpgradeTemplateV3.finishUpgrade +1.18. Call V3Template.finishUpgrade === NON-DG ITEMS === -2. Add AlterTiersInOperatorGrid factory to EasyTrack (permissions: operatorGrid, alterTiers); -3. Add RegisterGroupsInOperatorGrid factory to EasyTrack (permissions: operatorGrid, registerGroup + registerTiers); -4. Add RegisterTiersInOperatorGrid factory to EasyTrack (permissions: operatorGrid, registerTiers); -5. Add UpdateGroupsShareLimitInOperatorGrid factory to EasyTrack (permissions: operatorGrid, updateGroupShareLimit); -6. Add SetJailStatusInOperatorGrid factory to EasyTrack (permissions: vaultsAdapter, setVaultJailStatus); -7. Add UpdateVaultsFeesInOperatorGrid factory to EasyTrack (permissions: vaultsAdapter, updateVaultFees); -8. Add ForceValidatorExitsInVaultHub factory to EasyTrack (permissions: vaultsAdapter, forceValidatorExit); -9. Add SetLiabilitySharesTargetInVaultHub factory to EasyTrack (permissions: vaultsAdapter, setLiabilitySharesTarget); -10. Add SocializeBadDebtInVaultHub factory to EasyTrack (permissions: vaultsAdapter, socializeBadDebt). +2. Add AlterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid, alterTiers) +3. Add RegisterGroupsInOperatorGrid factory to Easy Track (permissions: operatorGrid, registerGroup + registerTiers) +4. Add RegisterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid, registerTiers) +5. Add UpdateGroupsShareLimitInOperatorGrid factory to Easy Track (permissions: operatorGrid, updateGroupShareLimit) +6. Add SetJailStatusInOperatorGrid factory to Easy Track (permissions: vaultsAdapter, setVaultJailStatus) +7. Add UpdateVaultsFeesInOperatorGrid factory to Easy Track (permissions: vaultsAdapter, updateVaultFees) +8. Add ForceValidatorExitsInVaultHub factory to Easy Track (permissions: vaultsAdapter, forceValidatorExit) +9. Add SetLiabilitySharesTargetInVaultHub factory to Easy Track (permissions: vaultsAdapter, setLiabilitySharesTarget) +10. Add SocializeBadDebtInVaultHub factory to Easy Track (permissions: vaultsAdapter, socializeBadDebt) # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index cf6911f60..e68023935 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -191,7 +191,7 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): staking_router = interface.StakingRouter(STAKING_ROUTER) dg_items = [ - # 1.1. Check DG voting enactment is within daily time window (14:00 UTC - 23:00 UTC) + # 1.1. Ensure DG proposal execution is within daily time window (14:00 UTC - 23:00 UTC) ( DUAL_GOVERNANCE_TIME_CONSTRAINTS, interface.TimeConstraints(DUAL_GOVERNANCE_TIME_CONSTRAINTS).checkTimeWithinDayTimeAndEmit.encode_input( @@ -200,7 +200,7 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): ), ), - # 1.2. Call UpgradeTemplateV3.startUpgrade + # 1.2. Call V3Template.startUpgrade agent_forward([ (upgradeTemplate.address, upgradeTemplate.startUpgrade.encode_input()) ]), @@ -329,7 +329,7 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): ) ]), - # 1.18. Call UpgradeTemplateV3.finishUpgrade + # 1.18. Call V3Template.finishUpgrade agent_forward([ (upgradeTemplate.address, upgradeTemplate.finishUpgrade.encode_input()) ]), @@ -752,7 +752,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ==================== After DG proposal executed checks ================== # ========================================================================= - # Step 1.3: Validate Lido Locator implementation was updated + # Step 1.3: Upgrade LidoLocator implementation assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" # Step 1.5. Set Lido implementation in Kernel @@ -773,7 +773,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting assert not old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting after upgrade" - # Step 1.11: Validate Accounting Oracle implementation was updated + # Step 1.11: Upgrade AccountingOracle implementation assert get_ossifiable_proxy_impl(accounting_oracle_proxy) == ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be updated to the new value" # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido From 5d67822189571e88deea038ef3fd152c2a4f97e7 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 2 Dec 2025 11:28:21 +0300 Subject: [PATCH 069/178] test: remove redundant roles checks --- tests/test_2025_12_10_mainnet_v3.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index e68023935..a658333c1 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -436,7 +436,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g registry_role = web3.keccak(text="vaults.OperatorsGrid.Registry") assert operator_grid.hasRole(registry_role, VAULTS_ADAPTER), "Operator Grid should have REGISTRY_ROLE on VAULTS_ADAPTER after upgrade" assert operator_grid.hasRole(registry_role, EASYTRACK_EVMSCRIPT_EXECUTOR), "Operator Grid should have REGISTRY_ROLE on EASYTRACK_EVMSCRIPT_EXECUTOR after upgrade" - assert operator_grid.hasRole(registry_role, AGENT), "Operator Grid should have REGISTRY_ROLE on AGENT after upgrade" assert operator_grid.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Operator Grid should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" # Check Burner roles request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") @@ -447,8 +446,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert vault_hub.hasRole(pause_role, GATE_SEAL_V3), "Vault Hub should have PAUSE_ROLE on GATE_SEAL_V3 after upgrade" assert vault_hub.hasRole(pause_role, RESEAL_MANAGER), "Vault Hub should have PAUSE_ROLE on RESEAL_MANAGER after upgrade" assert vault_hub.hasRole(resume_role, RESEAL_MANAGER), "Vault Hub should have RESUME_ROLE on RESEAL_MANAGER after upgrade" - vault_master_role = web3.keccak(text="vaults.VaultHub.VaultMasterRole") - assert vault_hub.hasRole(vault_master_role, AGENT), "Vault Hub should have VAULT_MASTER_ROLE on AGENT after upgrade" validator_exit_role = web3.keccak(text="vaults.VaultHub.ValidatorExitRole") assert vault_hub.hasRole(validator_exit_role, VAULTS_ADAPTER), "Vault Hub should have VALIDATOR_EXIT_ROLE on VAULTS_ADAPTER after upgrade" bad_debt_master_role = web3.keccak(text="vaults.VaultHub.BadDebtMasterRole") @@ -752,7 +749,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ==================== After DG proposal executed checks ================== # ========================================================================= - # Step 1.3: Upgrade LidoLocator implementation + # Step 1.3: Validate Lido Locator implementation was updated assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" # Step 1.5. Set Lido implementation in Kernel @@ -773,7 +770,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting assert not old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting after upgrade" - # Step 1.11: Upgrade AccountingOracle implementation + # Step 1.11: Validate Accounting Oracle implementation was updated assert get_ossifiable_proxy_impl(accounting_oracle_proxy) == ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be updated to the new value" # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido From 8a18b86789fd1609bef12db4f369055c25b673ab Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 2 Dec 2025 11:52:42 +0300 Subject: [PATCH 070/178] test: add V3Template events checks --- tests/test_2025_12_10_mainnet_v3.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index a658333c1..e6bdbd054 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -644,6 +644,10 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # === DG EXECUTION EVENTS VALIDATION === + # 1.2. Call V3Template.startUpgrade + assert 'UpgradeStarted' in dg_events[1] + assert dg_events[1]['UpgradeStarted'][0]['_emitted_by'] == UPGRADE_TEMPLATE + # 1.3. Lido Locator upgrade events validate_proxy_upgrade_event(dg_events[2], LIDO_LOCATOR_IMPL, emitted_by=lido_locator_proxy) @@ -744,6 +748,9 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g emitted_by=oracle_daemon_config ) + # 1.18. Call V3Template.finishUpgrade + assert 'UpgradeFinished' in dg_events[17] + assert dg_events[17]['UpgradeFinished'][0]['_emitted_by'] == UPGRADE_TEMPLATE # ========================================================================= # ==================== After DG proposal executed checks ================== From b184fc22c693e1d365d2c88d7a38722d245963cc Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 2 Dec 2025 08:58:11 +0000 Subject: [PATCH 071/178] fix: items order --- scripts/vote_2025_12_10.py | 110 +++++++++++++------------- tests/test_2025_12_10.py | 155 ++++++++++++++++++------------------- 2 files changed, 131 insertions(+), 134 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 8bd82fbe2..639045ed3 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -3,39 +3,37 @@ === 1. DG PROPOPSAL === I. Change Curated Module fees -1.1. Change Curated Module (MODULE_ID = 1) module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +1.1. Change Curated Module (MODULE_ID = 1) fees in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999: Module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP -II. Raise SDVT stake share limit -1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 bps to 430 bps in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +II. Raise SDVT module stake share limit +1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 III. Reset Easy Track TRP limit 1.3. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO 1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months -IV. Grant Stonks allowed recipients management permissions to Easy Track EVM Script Executor +IV. Grant Easy Track EVM Script Executor permissions to add and remove recipients for stETH and stablecoin Stonks Easy Tracks 1.5 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 1.7 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 === NON-DG ITEMS === -V. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig -2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 - -VI. Add sUSDS to stablecoins Allowed Tokens Registry -3. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e -4. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca -5. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e - -VII. Add sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance -6. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 -7. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS - -VIII. Add Stonks allowed recipients management factories to Easy Track -8. Add Stonks stETH AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 -9. Add Stonks stETH RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 -10. Add Stonks stablecoins AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 -11. Add Stonks stablecoins RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 +V. Add Easy Track factories for managing recipients of Stonks stETH and stablecoins top-up factories +2. Add AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 +3. Add RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 +4. Add AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 +5. Add RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 + +VI. Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance +6. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e +7. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca +8. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e +9. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 +10. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS + +VII. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig +11. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ @@ -316,15 +314,35 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_call_script[0] ), ( - "2. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5", - make_matic_payout( - target_address=LOL_MS, - matic_in_wei=MATIC_FOR_TRANSFER, - reference="Transfer 508,106 MATIC from Treasury to Liquidity Observation Lab (LOL) Multisig", + "2. Add AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0", + add_evmscript_factory( + factory=STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_steth_allowed_recipients_registry, "addRecipient"), ), ), ( - "3. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", + "3. Add RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0", + add_evmscript_factory( + factory=STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_steth_allowed_recipients_registry, "removeRecipient"), + ), + ), + ( + "4. Add AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", + add_evmscript_factory( + factory=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "addRecipient"), + ), + ), + ( + "5. Add RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", + add_evmscript_factory( + factory=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "removeRecipient"), + ), + ), + ( + "6. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), @@ -333,11 +351,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "4. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca", + "7. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca", (stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.addToken.encode_input(SUSDS_TOKEN)) ), ( - "5. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", + "8. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.revokeRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), @@ -346,7 +364,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ) ), ( - "6. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86", + "9. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86", encode_permission_revoke( target_app=FINANCE, permission_name=CREATE_PAYMENTS_ROLE, @@ -354,7 +372,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "7. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS", + "10. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS", encode_permission_grant_p( target_app=FINANCE, permission_name=CREATE_PAYMENTS_ROLE, @@ -363,31 +381,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "8. Add Stonks stETH AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0", - add_evmscript_factory( - factory=STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_steth_allowed_recipients_registry, "addRecipient"), - ), - ), - ( - "9. Add Stonks stETH RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0", - add_evmscript_factory( - factory=STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_steth_allowed_recipients_registry, "removeRecipient"), - ), - ), - ( - "10. Add Stonks stablecoins AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", - add_evmscript_factory( - factory=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "addRecipient"), - ), - ), - ( - "11. Add Stonks stablecoins RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", - add_evmscript_factory( - factory=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "removeRecipient"), + "11. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5", + make_matic_payout( + target_address=LOL_MS, + matic_in_wei=MATIC_FOR_TRANSFER, + reference="Transfer 508,106 MATIC from Treasury to Liquidity Observation Lab (LOL) Multisig", ), ), ) diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 2bed6587f..d10ae4f88 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -499,19 +499,21 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # Item 1 is DG - skipped here - # Item 2 - matic_treasury_balance_before = matic_token.balanceOf(agent.address) - assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE - matic_labs_balance_before = matic_token.balanceOf(LOL_MS) - assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE + # Items 2-5 + et_factories_before = easy_track.getEVMScriptFactories() + assert len(et_factories_before) == ET_FACTORIES_LEN_BEFORE + assert STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY not in et_factories_before + assert STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY not in et_factories_before + assert STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY not in et_factories_before + assert STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY not in et_factories_before - # Items 3,5 + # Items 6,8 assert not stablecoins_allowed_tokens_registry.hasRole( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING ) - # Item 4 + # Item 7 assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() assert len(allowed_tokens_before) == ALLOWED_TOKENS_BEFORE @@ -519,7 +521,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert allowed_tokens_before[1] == USDT_TOKEN assert allowed_tokens_before[2] == USDC_TOKEN - # Items 6,7 + # Items 9,10 assert acl.getPermissionParamsLength( ET_EVM_SCRIPT_EXECUTOR, FINANCE, @@ -536,13 +538,11 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert op == amount_limits_before()[i].op.value assert val == amount_limits_before()[i].value - # Items 8-11 - et_factories_before = easy_track.getEVMScriptFactories() - assert len(et_factories_before) == ET_FACTORIES_LEN_BEFORE - assert STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY not in et_factories_before - assert STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY not in et_factories_before - assert STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY not in et_factories_before - assert STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY not in et_factories_before + # Item 11 + matic_treasury_balance_before = matic_token.balanceOf(agent.address) + assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE + matic_labs_balance_before = matic_token.balanceOf(LOL_MS) + assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH @@ -558,24 +558,21 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # Item 1 is DG - skipped here - # Item 2 - matic_treasury_balance_after = matic_token.balanceOf(agent.address) - assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER - matic_labs_balance_after = matic_token.balanceOf(LOL_MS) - assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER - - # make sure LOL can actually spend the received MATIC - matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) - assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 - assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 + # Items 2-5 + et_factories_after = easy_track.getEVMScriptFactories() + assert len(et_factories_after) == ET_FACTORIES_LEN_AFTER + assert STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY in et_factories_after + assert STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY in et_factories_after + assert STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY in et_factories_after + assert STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY in et_factories_after - # Items 3,5 + # Items 6,8 assert not stablecoins_allowed_tokens_registry.hasRole( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING ) - # Item 4 + # Item 7 assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() assert len(allowed_tokens_before) == ALLOWED_TOKENS_AFTER @@ -584,7 +581,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert allowed_tokens_before[2] == USDC_TOKEN assert allowed_tokens_before[3] == SUSDS_TOKEN - # Items 6,7 + # Items 9,10 assert acl.getPermissionParamsLength( ET_EVM_SCRIPT_EXECUTOR, FINANCE, @@ -665,13 +662,15 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit + 1], ) - # Items 8-11 - et_factories_after = easy_track.getEVMScriptFactories() - assert len(et_factories_after) == ET_FACTORIES_LEN_AFTER - assert STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY in et_factories_after - assert STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY in et_factories_after - assert STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY in et_factories_after - assert STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY in et_factories_after + # Item 11 + matic_treasury_balance_after = matic_token.balanceOf(agent.address) + assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER + matic_labs_balance_after = matic_token.balanceOf(LOL_MS) + assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER + # make sure LOL can actually spend the received MATIC + matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) + assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 + assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 # scenario tests for new factories chain.snapshot() @@ -695,37 +694,59 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g ) # validate all other voting events - validate_token_payout_event( + validate_evmscript_factory_added_event( event=vote_events[1], - p=Payout( - token_addr=MATIC_TOKEN, - from_addr=AGENT, - to_addr=LOL_MS, - amount=MATIC_IN_LIDO_LABS_AFTER), - is_steth=False, - emitted_by=AGENT + p=EVMScriptFactoryAdded( + factory_addr=STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_steth_allowed_recipients_registry, "addRecipient"), + ), + emitted_by=EASY_TRACK, + ) + validate_evmscript_factory_added_event( + event=vote_events[2], + p=EVMScriptFactoryAdded( + factory_addr=STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_steth_allowed_recipients_registry, "removeRecipient"), + ), + emitted_by=EASY_TRACK, + ) + validate_evmscript_factory_added_event( + event=vote_events[3], + p=EVMScriptFactoryAdded( + factory_addr=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "addRecipient"), + ), + emitted_by=EASY_TRACK, + ) + validate_evmscript_factory_added_event( + event=vote_events[4], + p=EVMScriptFactoryAdded( + factory_addr=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, + permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "removeRecipient"), + ), + emitted_by=EASY_TRACK, ) validate_grant_role_event( - events=vote_events[2], + events=vote_events[5], role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), grant_to=VOTING, sender=VOTING, emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, ) validate_add_token_event( - event=vote_events[3], + event=vote_events[6], token=SUSDS_TOKEN, emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY ) validate_revoke_role_event( - events=vote_events[4], + events=vote_events[7], role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), revoke_from=VOTING, sender=VOTING, emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, ) validate_permission_revoke_event( - event=vote_events[5], + event=vote_events[8], p=Permission( app=FINANCE, entity=ET_EVM_SCRIPT_EXECUTOR, @@ -734,7 +755,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g emitted_by=ACL, ) validate_permission_grantp_event( - event=vote_events[6], + event=vote_events[9], p=Permission( app=FINANCE, entity=ET_EVM_SCRIPT_EXECUTOR, @@ -743,37 +764,15 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g params=amount_limits_after(), emitted_by=ACL, ) - validate_evmscript_factory_added_event( - event=vote_events[7], - p=EVMScriptFactoryAdded( - factory_addr=STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_steth_allowed_recipients_registry, "addRecipient"), - ), - emitted_by=EASY_TRACK, - ) - validate_evmscript_factory_added_event( - event=vote_events[8], - p=EVMScriptFactoryAdded( - factory_addr=STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_steth_allowed_recipients_registry, "removeRecipient"), - ), - emitted_by=EASY_TRACK, - ) - validate_evmscript_factory_added_event( - event=vote_events[9], - p=EVMScriptFactoryAdded( - factory_addr=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "addRecipient"), - ), - emitted_by=EASY_TRACK, - ) - validate_evmscript_factory_added_event( + validate_token_payout_event( event=vote_events[10], - p=EVMScriptFactoryAdded( - factory_addr=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "removeRecipient"), - ), - emitted_by=EASY_TRACK, + p=Payout( + token_addr=MATIC_TOKEN, + from_addr=AGENT, + to_addr=LOL_MS, + amount=MATIC_IN_LIDO_LABS_AFTER), + is_steth=False, + emitted_by=AGENT ) # ======================================================================= From 10b4e09e97fb7b496b52468e924b2d50ee5a52dc Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 10:10:21 +0000 Subject: [PATCH 072/178] ci: add runners for core and dg tests --- .github/workflows/core_tests.yml | 4 ++-- .github/workflows/dual_governance_regression.yml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 2ab539103..c1c5f3bed 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -9,6 +9,7 @@ on: - "feat/rc2" - "feat/rc1" - "feat/next-vote" + - "feat/v3-vote-ci" schedule: - cron: "0 0 * * TUE" @@ -19,7 +20,6 @@ jobs: timeout-minutes: 120 services: - tests-runner: image: ghcr.io/lidofinance/scripts:v20 ports: @@ -36,7 +36,7 @@ jobs: - name: Run init script run: docker exec -e CORE_BRANCH tests-runner bash -c 'make init' env: - CORE_BRANCH: master + CORE_BRANCH: feat/vaults-scripts - name: Run node run: docker exec -e ETH_RPC_URL --detach tests-runner bash -c 'NODE_PORT=8546 make node' diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index 0e2bf8aff..c075be554 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -13,6 +13,7 @@ on: - "feat/rc2" - "feat/rc1" - "feat/next-vote" + - "feat/v3-vote-ci" workflow_dispatch: jobs: @@ -26,15 +27,14 @@ jobs: cancel-in-progress: true env: - NODE_VERSION: '20' - PYTHON_VERSION: '3.10' - POETRY_VERSION: '1.8.2' - HARDHAT_NODE_URL: 'http://127.0.0.1:8545' - ANVIL_NODE_URL: 'http://127.0.0.1:8555' + NODE_VERSION: "20" + PYTHON_VERSION: "3.10" + POETRY_VERSION: "1.8.2" + HARDHAT_NODE_URL: "http://127.0.0.1:8545" + ANVIL_NODE_URL: "http://127.0.0.1:8555" CURL_PARAMS: '-X POST -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[],\"id\":1}"' steps: - ################################################################ # Clone repositories` ################################################################ @@ -63,7 +63,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'yarn' + cache: "yarn" cache-dependency-path: scripts/yarn.lock - name: Install Poetry From 563ad4d4f737e566a97aca22508123762eb8f5bc Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 10:58:43 +0000 Subject: [PATCH 073/178] feat: add v3 pre-deplyment artifacts --- .github/workflows/core_tests.yml | 1 + Makefile | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index c1c5f3bed..b6301bafa 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -56,3 +56,4 @@ jobs: run: docker exec -e CORE_TESTS_TARGET_RPC_URL tests-runner bash -c 'make test-core' env: CORE_TESTS_TARGET_RPC_URL: http://127.0.0.1:8546 + NETWORK_STATE_FILE: deployed-mainnet-v3.json diff --git a/Makefile b/Makefile index e870ae11a..526ac9731 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ CORE_DIR ?= lido-core CORE_BRANCH ?= master NODE_PORT ?= 8545 SECONDARY_NETWORK ?= mfh-2 +NETWORK_STATE_FILE ?= deployed-mainnet.json test-1/2: poetry run brownie test tests/[tc]*.py tests/regression/test_staking_router_stake_distribution.py --durations=20 --network mfh-1 @@ -81,6 +82,7 @@ test-core: cd $(CORE_DIR) && \ FORK_RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ + NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ yarn test:integration slots: From 7690f2be8090c6ef940ebb4c758de47fddff9c22 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 13:55:15 +0000 Subject: [PATCH 074/178] fix: push state file to runner --- .github/workflows/core_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index b6301bafa..46762fb81 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -53,7 +53,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run core tests - run: docker exec -e CORE_TESTS_TARGET_RPC_URL tests-runner bash -c 'make test-core' + run: docker exec -e CORE_TESTS_TARGET_RPC_URL -e NETWORK_STATE_FILE tests-runner bash -c 'make test-core' env: CORE_TESTS_TARGET_RPC_URL: http://127.0.0.1:8546 NETWORK_STATE_FILE: deployed-mainnet-v3.json From 357b23ceff075d21dab31743c74878c7658630a8 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 14:23:36 +0000 Subject: [PATCH 075/178] feat: trace --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 526ac9731..9f4d13a80 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ test-core: FORK_RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ - yarn test:integration + yarn test:integration:trace slots: @echo "Input https://github.com/lidofinance/protocol-onchain-mon-bots/blob/main/bots/ethereum-steth-v2/src/utils/constants.ts file content (end with Enter and Ctrl+D):" From 1a8d3f13971e422f044fce22946da04c4ab0ef1f Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 16:12:32 +0000 Subject: [PATCH 076/178] feat: pass latest block to the core tests --- Makefile | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9f4d13a80..5c609224f 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,12 @@ define run_2nd_test poetry run $(1) endef +__get_rpc_latest_block_number: + @curl -s -X POST $(CORE_TESTS_TARGET_RPC_URL) \ + -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + | sed -E 's/.*"result":"([^"]+)".*/\1/' \ + | xargs printf "%d" test: ifdef vote @@ -79,10 +85,12 @@ node3: npx hardhat node --fork $(ETH_RPC_URL3) --port $(NODE_PORT) test-core: + FORKING_BLOCK_NUMBER=$$($(MAKE) --no-print-directory __get_rpc_latest_block_number) && \ cd $(CORE_DIR) && \ - FORK_RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ + echo "FORKING_BLOCK_NUMBER: $$FORKING_BLOCK_NUMBER" && \ RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ + FORKING_BLOCK_NUMBER=$$FORKING_BLOCK_NUMBER \ yarn test:integration:trace slots: From 7b35c5e26f5ae5161d32891474b534f0a14651fb Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 16:39:45 +0000 Subject: [PATCH 077/178] feat: bump node --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 44b4acc37..0330f1f47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nikolaik/python-nodejs:python3.10-nodejs18 +FROM nikolaik/python-nodejs:python3.10-nodejs22-slim USER root ARG TARGETARCH From f53c2ec0b3d2d5f4fdec1864e4b573744303acd5 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 2 Dec 2025 19:58:59 +0300 Subject: [PATCH 078/178] test: update V3 test --- tests/acceptance/test_lido.py | 5 +- tests/test_2025_12_10_mainnet_v3.py | 342 ++++++++++++++++++--------- utils/test/event_validators/proxy.py | 17 ++ 3 files changed, 247 insertions(+), 117 deletions(-) diff --git a/tests/acceptance/test_lido.py b/tests/acceptance/test_lido.py index c476c2fb3..d348e9026 100644 --- a/tests/acceptance/test_lido.py +++ b/tests/acceptance/test_lido.py @@ -63,7 +63,10 @@ def test_petrified(): with reverts("INIT_ALREADY_INITIALIZED"): impl.initialize(contracts.lido_locator, contracts.eip712_steth, {"from": contracts.voting}) - with reverts("NOT_INITIALIZED"): # TODO: UNEXPECTED_CONTRACT_VERSION + # For petrified implementation, hasInitialized() returns false because + # AragonApp (LIDO) sets initializationBlock to PETRIFIED_BLOCK = uint256(-1) + # and hasInitialized() requires getBlockNumber() >= initializationBlock. + with reverts("NOT_INITIALIZED"): impl.finalizeUpgrade_v3(contracts.burner, [contracts.eip712_steth], 0, {"from": contracts.voting}) diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index e6bdbd054..e45aab0ae 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -22,6 +22,8 @@ from utils.easy_track import create_permissions from brownie.network.event import EventDict from utils.test.event_validators.common import validate_events_chain +from utils.test.event_validators.proxy import validate_proxy_upgrade_event +from utils.test.event_validators.permission import validate_grant_role_event, validate_revoke_role_event # ============================================================================ @@ -103,70 +105,171 @@ # ============================== Helper functions ============================ # ============================================================================ -def validate_proxy_upgrade_event(event: EventDict, implementation: str, emitted_by: Optional[str] = None, events_chain: Optional[list] = None): - _events_chain = events_chain or ["LogScriptCall", "Upgraded", "ScriptResult", "Executed"] - validate_events_chain([e.name for e in event], _events_chain) +def get_ossifiable_proxy_impl(proxy_address): + """Get implementation address from an OssifiableProxy""" + proxy = interface.OssifiableProxy(proxy_address) + return proxy.proxy__getImplementation() - assert event.count("LogScriptCall") == 1 - assert event.count("Upgraded") == 1 - assert "Upgraded" in event, "No Upgraded event found" +# ============================================================================ +# =================== Aragon event validators for DG ========================= +# ============================================================================ - assert event["Upgraded"][0]["implementation"] == implementation, "Wrong implementation address" +def validate_aragon_grant_permission_event( + event, + entity: str, + app: str, + role: str, + emitted_by: str, +) -> None: + """ + Validate Aragon ACL SetPermission event for granting permission via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetPermission", "ScriptResult", "Executed"] - if emitted_by is not None: - assert convert.to_address(event["Upgraded"][0]["_emitted_by"]) == convert.to_address( - emitted_by), "Wrong event emitter" + validate_events_chain([e.name for e in event], _events_chain) + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetPermission") == 1, f"Expected 1 SetPermission, got {event.count('SetPermission')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" + + assert event["SetPermission"]["allowed"] is True, "Permission should be granted (allowed=True)" + assert event["SetPermission"]["entity"] == entity, f"Wrong entity: expected {entity}, got {event['SetPermission']['entity']}" + assert event["SetPermission"]["app"] == app, f"Wrong app: expected {app}, got {event['SetPermission']['app']}" + assert event["SetPermission"]["role"] == role, f"Wrong role: expected {role}, got {event['SetPermission']['role']}" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" + + +def validate_aragon_revoke_permission_event( + event, + entity: str, + app: str, + role: str, + emitted_by: str, +) -> None: + """ + Validate Aragon ACL SetPermission event for revoking permission via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetPermission", "ScriptResult", "Executed"] -def validate_role_grant_event(event: EventDict, role_hash: str, account: str, emitted_by: Optional[str] = None): - _events_chain = ["LogScriptCall", "RoleGranted", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) - assert event.count("LogScriptCall") == 1 - assert event.count("RoleGranted") == 1 + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetPermission") == 1, f"Expected 1 SetPermission, got {event.count('SetPermission')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" + + assert event["SetPermission"]["allowed"] is False, "Permission should be revoked (allowed=False)" + assert event["SetPermission"]["entity"] == entity, f"Wrong entity: expected {entity}, got {event['SetPermission']['entity']}" + assert event["SetPermission"]["app"] == app, f"Wrong app: expected {app}, got {event['SetPermission']['app']}" + assert event["SetPermission"]["role"] == role, f"Wrong role: expected {role}, got {event['SetPermission']['role']}" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" + + +def validate_set_app_event( + event, + app_id: str, + app: str, + emitted_by: str, +) -> None: + """ + Validate Aragon Kernel SetApp event via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetApp", "ScriptResult", "Executed"] - assert "RoleGranted" in event, "No RoleGranted event found" + validate_events_chain([e.name for e in event], _events_chain) - # Strip 0x prefix for consistent comparison - expected_role_hash = role_hash.replace('0x', '') - actual_role_hash = event["RoleGranted"][0]["role"].hex().replace('0x', '') + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetApp") == 1, f"Expected 1 SetApp, got {event.count('SetApp')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" - assert actual_role_hash == expected_role_hash, "Wrong role hash" + assert event["SetApp"]["appId"] == app_id, f"Wrong appId: expected {app_id}, got {event['SetApp']['appId']}" + assert event["SetApp"]["app"] == app, f"Wrong app: expected {app}, got {event['SetApp']['app']}" - assert convert.to_address(event["RoleGranted"][0]["account"]) == convert.to_address(account), "Wrong account" + assert convert.to_address(event["SetApp"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" - if emitted_by is not None: - assert convert.to_address(event["RoleGranted"][0]["_emitted_by"]) == convert.to_address( - emitted_by), "Wrong event emitter" +def validate_config_value_set_event( + event, + key: str, + value: int, + emitted_by: str, +) -> None: + """ + Validate OracleDaemonConfig ConfigValueSet event via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "ConfigValueSet", "ScriptResult", "Executed"] -def validate_role_revoke_event(event: EventDict, role_hash: str, account: str, emitted_by: Optional[str] = None): - _events_chain = ["LogScriptCall", "RoleRevoked", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) - assert event.count("LogScriptCall") == 1 - assert event.count("RoleRevoked") == 1 + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("ConfigValueSet") == 1, f"Expected 1 ConfigValueSet, got {event.count('ConfigValueSet')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" - assert "RoleRevoked" in event, "No RoleRevoked event found" + assert key == event["ConfigValueSet"][0]["key"], f"Wrong key: expected {key} to be equal to {event['ConfigValueSet'][0]['key']}" + assert convert.to_int(event["ConfigValueSet"][0]["value"]) == value, f"Wrong value: expected {value}, got {convert.to_int(event['ConfigValueSet'][0]['value'])}" - # Strip 0x prefix for consistent comparison - expected_role_hash = role_hash.replace('0x', '') - actual_role_hash = event["RoleRevoked"][0]["role"].hex().replace('0x', '') + assert convert.to_address(event["ConfigValueSet"][0]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" - assert actual_role_hash == expected_role_hash, "Wrong role hash" - assert convert.to_address(event["RoleRevoked"][0]["account"]) == convert.to_address(account), "Wrong account" +def validate_upgrade_started_event(events) -> None: + """ + Validate V3Template UpgradeStarted event via DG proposal. + Ensures only expected events are fired. + """ + _events_chain = ["LogScriptCall", "UpgradeStarted", "ScriptResult", "Executed"] - if emitted_by is not None: - assert convert.to_address(event["RoleRevoked"][0]["_emitted_by"]) == convert.to_address( - emitted_by), "Wrong event emitter" + validate_events_chain([e.name for e in events], _events_chain) + assert events.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {events.count('LogScriptCall')}" + assert events.count("UpgradeStarted") == 1, f"Expected 1 UpgradeStarted, got {events.count('UpgradeStarted')}" + assert events.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {events.count('ScriptResult')}" + assert events.count("Executed") == 1, f"Expected 1 Executed, got {events.count('Executed')}" -def get_ossifiable_proxy_impl(proxy_address): - """Get implementation address from an OssifiableProxy""" - proxy = interface.OssifiableProxy(proxy_address) - return proxy.proxy__getImplementation() + assert convert.to_address(events["UpgradeStarted"][0]["_emitted_by"]) == convert.to_address( + UPGRADE_TEMPLATE + ), f"Wrong event emitter: expected {UPGRADE_TEMPLATE}" + + +def validate_upgrade_finished_event(events) -> None: + """ + Validate V3Template UpgradeFinished event via DG proposal. + Ensures only expected events are fired. + """ + _events_chain = ["LogScriptCall", "ContractVersionSet", "Approval", "Approval", "Approval", "Approval", + "MaxExternalRatioBPSet", "ContractVersionSet", "ConsensusVersionSet", "UpgradeFinished", "ScriptResult", "Executed"] + + validate_events_chain([e.name for e in events], _events_chain) + + assert events.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {events.count('LogScriptCall')}" + assert events.count("ContractVersionSet") == 2, f"Expected 1 ContractVersionSet, got {events.count('ContractVersionSet')}" + assert events.count("Approval") == 4, f"Expected 4 Approval, got {events.count('Approval')}" + assert events.count("MaxExternalRatioBPSet") == 1, f"Expected 1 MaxExternalRatioBPSet, got {events.count('MaxExternalRatioBPSet')}" + assert events.count("ConsensusVersionSet") == 1, f"Expected 1 ConsensusVersionSet, got {events.count('ConsensusVersionSet')}" + assert events.count("UpgradeFinished") == 1, f"Expected 1 UpgradeFinished, got {events.count('UpgradeFinished')}" + assert events.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {events.count('ScriptResult')}" + assert events.count("Executed") == 1, f"Expected 1 Executed, got {events.count('Executed')}" + + assert convert.to_address(events["UpgradeFinished"][0]["_emitted_by"]) == convert.to_address( + UPGRADE_TEMPLATE + ), f"Wrong event emitter: expected {UPGRADE_TEMPLATE}" # ============================================================================ @@ -424,33 +527,14 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================= After voting checks ========================= # ======================================================================= - # Deployment state checks: - # Check Predeposit Guarantee roles - pause_role = web3.keccak(text="PausableUntilWithRoles.PauseRole") - resume_role = web3.keccak(text="PausableUntilWithRoles.ResumeRole") - assert predeposit_guarantee.hasRole(pause_role, GATE_SEAL_V3), "Predeposit Guarantee should have PAUSE_ROLE on GATE_SEAL_V3 after upgrade" - assert predeposit_guarantee.hasRole(pause_role, RESEAL_MANAGER), "Predeposit Guarantee should have PAUSE_ROLE on RESEAL_MANAGER after upgrade" - assert predeposit_guarantee.hasRole(resume_role, RESEAL_MANAGER), "Predeposit Guarantee should have RESUME_ROLE on RESEAL_MANAGER after upgrade" - assert predeposit_guarantee.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Predeposit Guarantee should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" - # Check Operator Grid roles + # Check roles that are needed for Easy Track factories registry_role = web3.keccak(text="vaults.OperatorsGrid.Registry") assert operator_grid.hasRole(registry_role, VAULTS_ADAPTER), "Operator Grid should have REGISTRY_ROLE on VAULTS_ADAPTER after upgrade" assert operator_grid.hasRole(registry_role, EASYTRACK_EVMSCRIPT_EXECUTOR), "Operator Grid should have REGISTRY_ROLE on EASYTRACK_EVMSCRIPT_EXECUTOR after upgrade" - assert operator_grid.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Operator Grid should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" - # Check Burner roles - request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") - assert burner.hasRole(request_burn_shares_role, ACCOUNTING), "Burner should have REQUEST_BURN_SHARES_ROLE on ACCOUNTING after upgrade" - assert burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Burner should have REQUEST_BURN_SHARES_ROLE on CSM_ACCOUNTING after upgrade" - assert burner.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Burner should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" - # Check Vault Hub roles - assert vault_hub.hasRole(pause_role, GATE_SEAL_V3), "Vault Hub should have PAUSE_ROLE on GATE_SEAL_V3 after upgrade" - assert vault_hub.hasRole(pause_role, RESEAL_MANAGER), "Vault Hub should have PAUSE_ROLE on RESEAL_MANAGER after upgrade" - assert vault_hub.hasRole(resume_role, RESEAL_MANAGER), "Vault Hub should have RESUME_ROLE on RESEAL_MANAGER after upgrade" validator_exit_role = web3.keccak(text="vaults.VaultHub.ValidatorExitRole") assert vault_hub.hasRole(validator_exit_role, VAULTS_ADAPTER), "Vault Hub should have VALIDATOR_EXIT_ROLE on VAULTS_ADAPTER after upgrade" bad_debt_master_role = web3.keccak(text="vaults.VaultHub.BadDebtMasterRole") assert vault_hub.hasRole(bad_debt_master_role, VAULTS_ADAPTER), "Vault Hub should have BAD_DEBT_MASTER_ROLE on VAULTS_ADAPTER after upgrade" - assert vault_hub.hasRole(DEFAULT_ADMIN_ROLE, AGENT), "Vault Hub` should have DEFAULT_ADMIN_ROLE on AGENT after upgrade" # Steps 2-10: Add EasyTrack factories new_factories = easy_track.getEVMScriptFactories() @@ -645,112 +729,138 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # === DG EXECUTION EVENTS VALIDATION === # 1.2. Call V3Template.startUpgrade - assert 'UpgradeStarted' in dg_events[1] - assert dg_events[1]['UpgradeStarted'][0]['_emitted_by'] == UPGRADE_TEMPLATE + validate_upgrade_started_event(dg_events[1]) # 1.3. Lido Locator upgrade events validate_proxy_upgrade_event(dg_events[2], LIDO_LOCATOR_IMPL, emitted_by=lido_locator_proxy) # 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT - assert 'SetPermission' in dg_events[3] - assert dg_events[3]['SetPermission'][0]['allowed'] is True - assert dg_events[3]['SetPermission'][0]['_emitted_by'] == ACL - assert dg_events[3]['SetPermission'][0]['entity'] == AGENT - assert dg_events[3]['SetPermission'][0]['role'] == app_manager_role.hex() + validate_aragon_grant_permission_event( + dg_events[3], + entity=AGENT, + app=ARAGON_KERNEL, + role=app_manager_role.hex(), + emitted_by=ACL, + ) # 1.5. Set Lido implementation in Kernel - assert 'SetApp' in dg_events[4] - assert dg_events[4]['SetApp'][0]['appId'] == LIDO_APP_ID - assert dg_events[4]['SetApp'][0]['_emitted_by'] == ARAGON_KERNEL - assert dg_events[4]['SetApp'][0]['app'] == LIDO_IMPL + validate_set_app_event( + dg_events[4], + app_id=LIDO_APP_ID, + app=LIDO_IMPL, + emitted_by=ARAGON_KERNEL, + ) # 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT - assert 'SetPermission' in dg_events[5] - assert dg_events[5]['SetPermission'][0]['allowed'] is False - assert dg_events[5]['SetPermission'][0]['_emitted_by'] == ACL - assert dg_events[5]['SetPermission'][0]['entity'] == AGENT - assert dg_events[5]['SetPermission'][0]['role'] == app_manager_role.hex() + validate_aragon_revoke_permission_event( + dg_events[5], + entity=AGENT, + app=ARAGON_KERNEL, + role=app_manager_role.hex(), + emitted_by=ACL, + ) # 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido - validate_role_revoke_event( + validate_revoke_role_event( dg_events[6], - role_hash=request_burn_shares_role.hex(), - account=STETH, - emitted_by=old_burner + role=request_burn_shares_role.hex(), + revoke_from=STETH, + sender=AGENT, + emitted_by=old_burner, + is_dg_event=True ) # 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module - validate_role_revoke_event( + validate_revoke_role_event( dg_events[7], - role_hash=request_burn_shares_role.hex(), - account=NODE_OPERATORS_REGISTRY, - emitted_by=old_burner + role=request_burn_shares_role.hex(), + revoke_from=NODE_OPERATORS_REGISTRY, + sender=AGENT, + emitted_by=old_burner, + is_dg_event=True ) # 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT - validate_role_revoke_event( + validate_revoke_role_event( dg_events[8], - role_hash=request_burn_shares_role.hex(), - account=SIMPLE_DVT, - emitted_by=old_burner + role=request_burn_shares_role.hex(), + revoke_from=SIMPLE_DVT, + sender=AGENT, + emitted_by=old_burner, + is_dg_event=True ) # 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting - validate_role_revoke_event( + validate_revoke_role_event( dg_events[9], - role_hash=request_burn_shares_role.hex(), - account=CSM_ACCOUNTING, - emitted_by=old_burner + role=request_burn_shares_role.hex(), + revoke_from=CSM_ACCOUNTING, + sender=AGENT, + emitted_by=old_burner, + is_dg_event=True ) # 1.11. Accounting Oracle upgrade events validate_proxy_upgrade_event(dg_events[10], ACCOUNTING_ORACLE_IMPL, emitted_by=accounting_oracle_proxy) # 1.12. Revoke Staking Router REPORT_REWARDS_MINTED_ROLE from the Lido - validate_role_revoke_event( + validate_revoke_role_event( dg_events[11], - role_hash=report_rewards_minted_role.hex(), - account=STETH, - emitted_by=staking_router + role=report_rewards_minted_role.hex(), + revoke_from=STETH, + sender=AGENT, + emitted_by=staking_router, + is_dg_event=True ) # 1.13. Grant Staking Router REPORT_REWARDS_MINTED_ROLE to Accounting - validate_role_grant_event( + validate_grant_role_event( dg_events[12], - role_hash=report_rewards_minted_role.hex(), - account=ACCOUNTING, - emitted_by=staking_router + role=report_rewards_minted_role.hex(), + grant_to=ACCOUNTING, + sender=AGENT, + emitted_by=staking_router, + is_dg_event=True ) # 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent - validate_role_grant_event( + validate_grant_role_event( dg_events[13], - role_hash=config_manager_role.hex(), - account=AGENT, - emitted_by=oracle_daemon_config + role=config_manager_role.hex(), + grant_to=AGENT, + sender=AGENT, + emitted_by=oracle_daemon_config, + is_dg_event=True ) # 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig - assert 'ConfigValueSet' in dg_events[14] - assert 'SLASHING_RESERVE_WE_RIGHT_SHIFT' in dg_events[14]['ConfigValueSet'][0]['key'] - assert convert.to_int(dg_events[14]['ConfigValueSet'][0]['value']) == SLASHING_RESERVE_SHIFT + validate_config_value_set_event( + dg_events[14], + key='SLASHING_RESERVE_WE_RIGHT_SHIFT', + value=SLASHING_RESERVE_SHIFT, + emitted_by=oracle_daemon_config, + ) # 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig - assert 'ConfigValueSet' in dg_events[15] - assert 'SLASHING_RESERVE_WE_LEFT_SHIFT' in dg_events[15]['ConfigValueSet'][0]['key'] - assert convert.to_int(dg_events[15]['ConfigValueSet'][0]['value']) == SLASHING_RESERVE_SHIFT + validate_config_value_set_event( + dg_events[15], + key='SLASHING_RESERVE_WE_LEFT_SHIFT', + value=SLASHING_RESERVE_SHIFT, + emitted_by=oracle_daemon_config, + ) # 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent - validate_role_revoke_event( + validate_revoke_role_event( dg_events[16], - role_hash=config_manager_role.hex(), - account=AGENT, - emitted_by=oracle_daemon_config + role=config_manager_role.hex(), + revoke_from=AGENT, + sender=AGENT, + emitted_by=oracle_daemon_config, + is_dg_event=True ) # 1.18. Call V3Template.finishUpgrade - assert 'UpgradeFinished' in dg_events[17] - assert dg_events[17]['UpgradeFinished'][0]['_emitted_by'] == UPGRADE_TEMPLATE + validate_upgrade_finished_event(dg_events[17]) # ========================================================================= # ==================== After DG proposal executed checks ================== diff --git a/utils/test/event_validators/proxy.py b/utils/test/event_validators/proxy.py index b7c19dcc6..6c1897452 100644 --- a/utils/test/event_validators/proxy.py +++ b/utils/test/event_validators/proxy.py @@ -1,6 +1,7 @@ from brownie import convert from brownie.network.event import EventDict from .common import validate_events_chain +from typing import Optional def validate_proxy_admin_changed(event: EventDict, prev_admin: str, new_admin: str, emitted_by: str = None) -> None: @@ -18,3 +19,19 @@ def validate_proxy_admin_changed(event: EventDict, prev_admin: str, new_admin: s assert convert.to_address(event["AdminChanged"]["_emitted_by"]) == convert.to_address( emitted_by ), "Wrong event emitter" + + +def validate_proxy_upgrade_event(event: EventDict, implementation: str, emitted_by: Optional[str] = None, events_chain: Optional[list] = None): + _events_chain = events_chain or ["LogScriptCall", "Upgraded", "ScriptResult", "Executed"] + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1 + assert event.count("Upgraded") == 1 + + assert "Upgraded" in event, "No Upgraded event found" + + assert event["Upgraded"][0]["implementation"] == implementation, "Wrong implementation address" + + if emitted_by is not None: + assert convert.to_address(event["Upgraded"][0]["_emitted_by"]) == convert.to_address( + emitted_by), "Wrong event emitter" From 97f59c720b0031f2cfe8e5c99fff1afb9e5be0a1 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 17:00:39 +0000 Subject: [PATCH 079/178] feat: bump node version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0330f1f47..21d35eb77 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nikolaik/python-nodejs:python3.10-nodejs22-slim +FROM nikolaik/python-nodejs:python3.10-nodejs24-slim USER root ARG TARGETARCH From ba0c20ee6f246130b77adfd6d1bd4f5d8bb6d10e Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 17:42:24 +0000 Subject: [PATCH 080/178] chore: remove trace --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5c609224f..388631453 100644 --- a/Makefile +++ b/Makefile @@ -91,7 +91,7 @@ test-core: RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ FORKING_BLOCK_NUMBER=$$FORKING_BLOCK_NUMBER \ - yarn test:integration:trace + yarn test:integration slots: @echo "Input https://github.com/lidofinance/protocol-onchain-mon-bots/blob/main/bots/ethereum-steth-v2/src/utils/constants.ts file content (end with Enter and Ctrl+D):" From ee8274ae9a672289a6b2c3cbd904dcf8aa2dffa3 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 2 Dec 2025 19:50:17 +0000 Subject: [PATCH 081/178] feat: add a41 soft limit reset --- scripts/vote_2025_12_10.py | 51 ++-- tests/test_2025_12_10.py | 223 +++++++++++------- .../node_operators_registry.py | 7 +- 3 files changed, 172 insertions(+), 109 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 8ecf301e9..206d07d50 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -8,31 +8,34 @@ II. Raise SDVT module stake share limit 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 -III. Reset Easy Track TRP limit -1.3. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO -1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months +III. Set A41 soft target validator limit to 0 +1.3. Set soft target validator limit for A41 (Node Operator ID = 32) in Curated Module (MODULE_ID = 1) to 0 -IV. Grant Easy Track EVM Script Executor permissions to add and remove recipients for stETH and stablecoin Stonks Easy Tracks -1.5 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 -1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 -1.7 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 -1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 +IV. Reset Easy Track TRP limit +1.4. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO +1.5. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + +V. Grant Easy Track EVM Script Executor permissions to add and remove recipients for stETH and stablecoin Stonks Easy Tracks +1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 +1.7 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 +1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 +1.9 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 === NON-DG ITEMS === -V. Add Easy Track factories for managing recipients of Stonks stETH and stablecoins top-up factories +VI. Add Easy Track factories for managing recipients of Stonks stETH and stablecoins top-up factories 2. Add AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 3. Add RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 4. Add AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 5. Add RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 -VI. Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance +VII. Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance 6. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e 7. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca 8. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e 9. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 10. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS -VII. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig +VIII. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig 11. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. @@ -64,6 +67,7 @@ class TokenLimit(NamedTuple): # ============================== Addresses =================================== +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" @@ -86,6 +90,7 @@ class TokenLimit(NamedTuple): ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE = "ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE" REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE = "REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE" +STAKING_MODULE_MANAGE_ROLE = "STAKING_MODULE_MANAGE_ROLE" # ============================== Tokens =================================== @@ -120,6 +125,10 @@ class TokenLimit(NamedTuple): MATIC_FOR_TRANSFER = 508_106 * 10**18 +A41_NO_ID = 32 +NO_TARGET_LIMIT_SOFT_MODE = 1 +NEW_A41_TARGET_LIMIT = 0 + def amount_limits() -> List[Param]: ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) @@ -219,7 +228,6 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: staking_router = interface.StakingRouter(STAKING_ROUTER) stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) - stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) stonks_stablecoins_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) @@ -255,11 +263,18 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.3. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO + # 1.3. Set soft target validator limit for A41 (Node Operator ID = 32) in Curated Module (MODULE_ID = 1) to 0 + ( + staking_router.address, + staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE, NEW_A41_TARGET_LIMIT), + ) + ]), + agent_forward([ + # 1.4. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), ]), agent_forward([ - # 1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + # 1.5. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months set_limit_parameters( limit=TRP_NEW_LIMIT, period_duration_months=TRP_PERIOD_DURATION_MONTHS, @@ -267,7 +282,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.5 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 + # 1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 ( stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), @@ -276,7 +291,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 + # 1.7 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 ( stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), @@ -285,7 +300,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.7 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 + # 1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 ( stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), @@ -294,7 +309,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 + # 1.9 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 ( stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index d10ae4f88..a8ff6a696 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -1,5 +1,6 @@ import pytest from typing import List, NamedTuple +from utils.test.event_validators.common import validate_events_chain from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 from brownie.network.transaction import TransactionReceipt @@ -49,6 +50,10 @@ validate_evmscript_factory_added_event, EVMScriptFactoryAdded, ) +from utils.test.event_validators.node_operators_registry import ( + validate_target_validators_count_changed_event, + TargetValidatorsCountChanged, +) class TokenLimit(NamedTuple): @@ -73,6 +78,7 @@ class TokenLimit(NamedTuple): EASY_TRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" ACL = "0x9895f0f17cc1d1891b6f18ee0b483b6f221b37bb" +CURATED_MODULE = "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" @@ -115,6 +121,7 @@ class TokenLimit(NamedTuple): REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE = "ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE" REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE = "REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE" +STAKING_MODULE_MANAGE_ROLE = "STAKING_MODULE_MANAGE_ROLE" # ============================== Constants =================================== @@ -157,6 +164,15 @@ class TokenLimit(NamedTuple): ET_FACTORIES_LEN_BEFORE = 33 ET_FACTORIES_LEN_AFTER = ET_FACTORIES_LEN_BEFORE + 4 +A41_NO_ID = 32 +NO_TARGET_LIMIT_SOFT_MODE_BEFORE = 0 +NO_TARGET_LIMIT_SOFT_MODE_AFTER = 1 +NEW_A41_TARGET_LIMIT = 0 +A41_TARGET_CHANGE_REQUEST = TargetValidatorsCountChanged( + nodeOperatorId=A41_NO_ID, + targetValidatorsCount=NEW_A41_TARGET_LIMIT, +) + # ============================== Tokens =================================== MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" @@ -173,7 +189,7 @@ class TokenLimit(NamedTuple): EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 EXPECTED_VOTE_EVENTS_COUNT = 11 -EXPECTED_DG_EVENTS_COUNT = 8 +EXPECTED_DG_EVENTS_COUNT = 9 IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" @@ -395,6 +411,12 @@ def dual_governance_proposal_calls(): ), ), ]), + agent_forward([ + ( + staking_router.address, + staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE_AFTER, NEW_A41_TARGET_LIMIT), + ) + ]), agent_forward([ unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), ]), @@ -780,96 +802,96 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # put a lot of tokens into Agent to check Finance/ET limits - prepare_agent_for_dai_payment(30_000_000 * 10**18) - prepare_agent_for_usdt_payment(30_000_000 * 10**6) - prepare_agent_for_usdc_payment(30_000_000 * 10**6) - prepare_agent_for_susds_payment(30_000_000 * 10**18) - prepare_agent_for_ldo_payment(10_000_000 * 10**18) - prepare_agent_for_steth_payment(2_000 * 10**18) + #prepare_agent_for_dai_payment(30_000_000 * 10**18) + #prepare_agent_for_usdt_payment(30_000_000 * 10**6) + #prepare_agent_for_usdc_payment(30_000_000 * 10**6) + #prepare_agent_for_susds_payment(30_000_000 * 10**18) + #prepare_agent_for_ldo_payment(10_000_000 * 10**18) + #prepare_agent_for_steth_payment(2_000 * 10**18) # check ET limits via Easy Track motion - ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 - et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, LEGO_LDO_SPENDABLE_BALANCE, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, GAS_SUPPLY_STETH_SPENDABLE_BALANCE, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + #ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 + #et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + #et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + #et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + #et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + #et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, LEGO_LDO_SPENDABLE_BALANCE, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + #et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, GAS_SUPPLY_STETH_SPENDABLE_BALANCE, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) # check Finance limits via Easy Track motion - finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY) + #finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + #finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + #finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + #finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + #finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY) + #finance_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY) # sUSDS can be removed after being added to the allowed list - chain.snapshot() - stablecoins_allowed_tokens_registry.grantRole( - convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), - VOTING, - {"from": VOTING} - ) - assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - stablecoins_allowed_tokens_registry.removeToken( - SUSDS_TOKEN, - {"from": VOTING} - ) - assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - with reverts("TOKEN_NOT_ALLOWED"): - create_and_enact_payment_motion( - interface.EasyTrack(EASY_TRACK), - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - interface.ERC20(SUSDS_TOKEN), - [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - [1 * 10**18], - stranger, - ) - chain.revert() + #chain.snapshot() + #stablecoins_allowed_tokens_registry.grantRole( + # convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), + # VOTING, + # {"from": VOTING} + #) + #assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + #stablecoins_allowed_tokens_registry.removeToken( + # SUSDS_TOKEN, + # {"from": VOTING} + #) + #assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + #with reverts("TOKEN_NOT_ALLOWED"): + # create_and_enact_payment_motion( + # interface.EasyTrack(EASY_TRACK), + # LIDO_LABS_TRUSTED_CALLER, + # LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + # interface.ERC20(SUSDS_TOKEN), + # [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + # [1 * 10**18], + # stranger, + # ) + #chain.revert() # spending tokens not from the allowed list should fail - chain.snapshot() - with reverts("TOKEN_NOT_ALLOWED"): - create_and_enact_payment_motion( - interface.EasyTrack(EASY_TRACK), - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - interface.ERC20(WSTETH_TOKEN), - [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - [1 * 10**18], - stranger, - ) - chain.revert() + #chain.snapshot() + #with reverts("TOKEN_NOT_ALLOWED"): + # create_and_enact_payment_motion( + # interface.EasyTrack(EASY_TRACK), + # LIDO_LABS_TRUSTED_CALLER, + # LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + # interface.ERC20(WSTETH_TOKEN), + # [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + # [1 * 10**18], + # stranger, + # ) + #chain.revert() # spending the allowed token not from the Finance CREATE_PAYMENTS_ROLE's list should fail - chain.snapshot() - stablecoins_allowed_tokens_registry.grantRole( - convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), - VOTING, - {"from": VOTING} - ) - assert not stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) - stablecoins_allowed_tokens_registry.addToken( - WSTETH_TOKEN, - {"from": VOTING} - ) - assert stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) - with reverts("APP_AUTH_FAILED"): - create_and_enact_payment_motion( - interface.EasyTrack(EASY_TRACK), - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - interface.ERC20(WSTETH_TOKEN), - [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - [1 * 10**18], - stranger, - ) - chain.revert() + #chain.snapshot() + #stablecoins_allowed_tokens_registry.grantRole( + # convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + # VOTING, + # {"from": VOTING} + #) + #assert not stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + #stablecoins_allowed_tokens_registry.addToken( + # WSTETH_TOKEN, + # {"from": VOTING} + #) + #assert stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + #with reverts("APP_AUTH_FAILED"): + # create_and_enact_payment_motion( + # interface.EasyTrack(EASY_TRACK), + # LIDO_LABS_TRUSTED_CALLER, + # LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + # interface.ERC20(WSTETH_TOKEN), + # [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + # [1 * 10**18], + # stranger, + # ) + #chain.revert() # happy path - usds_wrap_happy_path(stranger) + #usds_wrap_happy_path(stranger) if EXPECTED_DG_PROPOSAL_ID is not None: @@ -901,7 +923,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE assert sdvt_module_before['name'] == SDVT_MODULE_NAME - # Items 1.3,1.4 + # Item 1.3 + a41_summary_before = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) + assert a41_summary_before['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_BEFORE + assert a41_summary_before['depositableValidatorsCount'] > 0 + + # Items 1.4,1.5 trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() assert trp_limit_before == TRP_LIMIT_BEFORE @@ -910,7 +937,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP - # Items 1.5-1.8 + # Items 1.6-1.9 assert not stonks_steth_allowed_recipients_registry.hasRole( convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR @@ -970,41 +997,46 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), emitted_by=STAKING_ROUTER ) + validate_target_validators_count_changed_event( + event=dg_events[2], + t=A41_TARGET_CHANGE_REQUEST, + emitted_by=CURATED_MODULE, + ) validate_set_spent_amount_event( - dg_events[2], + dg_events[3], new_spent_amount=0, emitted_by=ET_TRP_REGISTRY, ) validate_set_limit_parameter_event( - dg_events[3], + dg_events[4], limit=TRP_LIMIT_AFTER, period_duration_month=TRP_PERIOD_DURATION_MONTHS, period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, emitted_by=ET_TRP_REGISTRY, ) validate_grant_role_event( - events=dg_events[4], + events=dg_events[5], role=web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE).hex(), grant_to=ET_EVM_SCRIPT_EXECUTOR, sender=AGENT, emitted_by=STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY, ) validate_grant_role_event( - events=dg_events[5], + events=dg_events[6], role=web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE).hex(), grant_to=ET_EVM_SCRIPT_EXECUTOR, sender=AGENT, emitted_by=STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY, ) validate_grant_role_event( - events=dg_events[6], + events=dg_events[7], role=web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE).hex(), grant_to=ET_EVM_SCRIPT_EXECUTOR, sender=AGENT, emitted_by=STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY, ) validate_grant_role_event( - events=dg_events[7], + events=dg_events[8], role=web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE).hex(), grant_to=ET_EVM_SCRIPT_EXECUTOR, sender=AGENT, @@ -1066,7 +1098,20 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) assert len(sdvt_module_after.items()) == 13 - # Items 1.3,1.4 + # Item 1.3 + a41_summary_after = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) + assert a41_summary_after['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_AFTER + assert a41_summary_after['depositableValidatorsCount'] == 0 + # additional checks to make sure no other fields were changed + assert a41_summary_after['targetValidatorsCount'] == a41_summary_before['targetValidatorsCount'] + assert a41_summary_after['stuckValidatorsCount'] == a41_summary_before['stuckValidatorsCount'] + assert a41_summary_after['refundedValidatorsCount'] == a41_summary_before['refundedValidatorsCount'] + assert a41_summary_after['stuckPenaltyEndTimestamp'] == a41_summary_before['stuckPenaltyEndTimestamp'] + assert a41_summary_after['totalExitedValidators'] == a41_summary_before['totalExitedValidators'] + assert a41_summary_after['totalDepositedValidators'] == a41_summary_before['totalDepositedValidators'] + assert len(a41_summary_after.items()) == 8 + + # Items 1.4,1.5 trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() assert trp_limit_after == TRP_LIMIT_AFTER @@ -1076,7 +1121,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - # Items 1.5-1.8 + # Items 1.6-1.9 assert stonks_steth_allowed_recipients_registry.hasRole( convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), ET_EVM_SCRIPT_EXECUTOR diff --git a/utils/test/event_validators/node_operators_registry.py b/utils/test/event_validators/node_operators_registry.py index 079583079..0fe3077bd 100644 --- a/utils/test/event_validators/node_operators_registry.py +++ b/utils/test/event_validators/node_operators_registry.py @@ -91,8 +91,8 @@ def validate_node_operator_reward_address_set_event( emitted_by ), "Wrong event emitter" -def validate_target_validators_count_changed_event(event: EventDict, t: TargetValidatorsCountChanged): - _events_chain = ["LogScriptCall", "LogScriptCall", "TargetValidatorsCountChanged", "KeysOpIndexSet", "NonceChanged", "ScriptResult"] +def validate_target_validators_count_changed_event(event: EventDict, t: TargetValidatorsCountChanged, emitted_by: str): + _events_chain = ['LogScriptCall', 'TargetValidatorsCountChanged', 'KeysOpIndexSet', 'NonceChanged', 'ScriptResult', 'Executed'] validate_events_chain([e.name for e in event], _events_chain) @@ -100,6 +100,9 @@ def validate_target_validators_count_changed_event(event: EventDict, t: TargetVa assert event["TargetValidatorsCountChanged"]["nodeOperatorId"] == t.nodeOperatorId assert event["TargetValidatorsCountChanged"]["targetValidatorsCount"] == t.targetValidatorsCount + assert convert.to_address(event["TargetValidatorsCountChanged"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_node_operator_deactivated(event: EventDict, node_operator_id: int): _events_chain = [ From c0db5cd8e033af0d828b5b37182062bd13246ea2 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 2 Dec 2025 20:03:54 +0000 Subject: [PATCH 082/178] feat: add a41 soft limit reset (ii) --- scripts/vote_2025_12_10.py | 2 - tests/test_2025_12_10.py | 238 +++++++++++++++++++------------------ 2 files changed, 121 insertions(+), 119 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 206d07d50..9108abad3 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -67,7 +67,6 @@ class TokenLimit(NamedTuple): # ============================== Addresses =================================== -AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" @@ -90,7 +89,6 @@ class TokenLimit(NamedTuple): ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE = "ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE" REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE = "REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE" -STAKING_MODULE_MANAGE_ROLE = "STAKING_MODULE_MANAGE_ROLE" # ============================== Tokens =================================== diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index a8ff6a696..0dc4cebbb 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -1,6 +1,5 @@ import pytest from typing import List, NamedTuple -from utils.test.event_validators.common import validate_events_chain from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 from brownie.network.transaction import TransactionReceipt @@ -121,7 +120,6 @@ class TokenLimit(NamedTuple): REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE = "ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE" REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE = "REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE" -STAKING_MODULE_MANAGE_ROLE = "STAKING_MODULE_MANAGE_ROLE" # ============================== Constants =================================== @@ -491,6 +489,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) stonks_stablecoins_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) easy_track = interface.EasyTrack(EASY_TRACK) + curated_module = interface.NodeOperatorsRegistry(CURATED_MODULE) # ========================================================================= @@ -802,96 +801,96 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ======================================================================= # put a lot of tokens into Agent to check Finance/ET limits - #prepare_agent_for_dai_payment(30_000_000 * 10**18) - #prepare_agent_for_usdt_payment(30_000_000 * 10**6) - #prepare_agent_for_usdc_payment(30_000_000 * 10**6) - #prepare_agent_for_susds_payment(30_000_000 * 10**18) - #prepare_agent_for_ldo_payment(10_000_000 * 10**18) - #prepare_agent_for_steth_payment(2_000 * 10**18) + prepare_agent_for_dai_payment(30_000_000 * 10**18) + prepare_agent_for_usdt_payment(30_000_000 * 10**6) + prepare_agent_for_usdc_payment(30_000_000 * 10**6) + prepare_agent_for_susds_payment(30_000_000 * 10**18) + prepare_agent_for_ldo_payment(10_000_000 * 10**18) + prepare_agent_for_steth_payment(2_000 * 10**18) # check ET limits via Easy Track motion - #ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 - #et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - #et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - #et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - #et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - #et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, LEGO_LDO_SPENDABLE_BALANCE, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - #et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, GAS_SUPPLY_STETH_SPENDABLE_BALANCE, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 + et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, LEGO_LDO_SPENDABLE_BALANCE, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, GAS_SUPPLY_STETH_SPENDABLE_BALANCE, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) # check Finance limits via Easy Track motion - #finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - #finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - #finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - #finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - #finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY) - #finance_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY) # sUSDS can be removed after being added to the allowed list - #chain.snapshot() - #stablecoins_allowed_tokens_registry.grantRole( - # convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), - # VOTING, - # {"from": VOTING} - #) - #assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - #stablecoins_allowed_tokens_registry.removeToken( - # SUSDS_TOKEN, - # {"from": VOTING} - #) - #assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - #with reverts("TOKEN_NOT_ALLOWED"): - # create_and_enact_payment_motion( - # interface.EasyTrack(EASY_TRACK), - # LIDO_LABS_TRUSTED_CALLER, - # LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - # interface.ERC20(SUSDS_TOKEN), - # [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - # [1 * 10**18], - # stranger, - # ) - #chain.revert() + chain.snapshot() + stablecoins_allowed_tokens_registry.grantRole( + convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), + VOTING, + {"from": VOTING} + ) + assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + stablecoins_allowed_tokens_registry.removeToken( + SUSDS_TOKEN, + {"from": VOTING} + ) + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + with reverts("TOKEN_NOT_ALLOWED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(SUSDS_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() # spending tokens not from the allowed list should fail - #chain.snapshot() - #with reverts("TOKEN_NOT_ALLOWED"): - # create_and_enact_payment_motion( - # interface.EasyTrack(EASY_TRACK), - # LIDO_LABS_TRUSTED_CALLER, - # LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - # interface.ERC20(WSTETH_TOKEN), - # [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - # [1 * 10**18], - # stranger, - # ) - #chain.revert() + chain.snapshot() + with reverts("TOKEN_NOT_ALLOWED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(WSTETH_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() # spending the allowed token not from the Finance CREATE_PAYMENTS_ROLE's list should fail - #chain.snapshot() - #stablecoins_allowed_tokens_registry.grantRole( - # convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), - # VOTING, - # {"from": VOTING} - #) - #assert not stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) - #stablecoins_allowed_tokens_registry.addToken( - # WSTETH_TOKEN, - # {"from": VOTING} - #) - #assert stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) - #with reverts("APP_AUTH_FAILED"): - # create_and_enact_payment_motion( - # interface.EasyTrack(EASY_TRACK), - # LIDO_LABS_TRUSTED_CALLER, - # LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - # interface.ERC20(WSTETH_TOKEN), - # [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - # [1 * 10**18], - # stranger, - # ) - #chain.revert() + chain.snapshot() + stablecoins_allowed_tokens_registry.grantRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING, + {"from": VOTING} + ) + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + stablecoins_allowed_tokens_registry.addToken( + WSTETH_TOKEN, + {"from": VOTING} + ) + assert stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + with reverts("APP_AUTH_FAILED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(WSTETH_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() # happy path - #usds_wrap_happy_path(stranger) + usds_wrap_happy_path(stranger) if EXPECTED_DG_PROPOSAL_ID is not None: @@ -927,6 +926,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g a41_summary_before = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) assert a41_summary_before['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_BEFORE assert a41_summary_before['depositableValidatorsCount'] > 0 + assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" # Items 1.4,1.5 trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() @@ -1057,20 +1057,21 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert curated_module_after['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK assert curated_module_after['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE assert curated_module_after['name'] == CURATED_MODULE_NAME - # additional checks to make sure no other fields were changed - assert curated_module_after['id'] == curated_module_before['id'] - assert curated_module_after['stakingModuleAddress'] == curated_module_before['stakingModuleAddress'] - assert curated_module_after['stakeShareLimit'] == curated_module_before['stakeShareLimit'] - assert curated_module_after['status'] == curated_module_before['status'] - assert curated_module_after['name'] == curated_module_before['name'] - assert curated_module_after['lastDepositAt'] == curated_module_before['lastDepositAt'] - assert curated_module_after['lastDepositBlock'] == curated_module_before['lastDepositBlock'] - assert curated_module_after['exitedValidatorsCount'] == curated_module_before['exitedValidatorsCount'] - assert curated_module_after['maxDepositsPerBlock'] == curated_module_before['maxDepositsPerBlock'] - assert curated_module_after['minDepositBlockDistance'] == curated_module_before['minDepositBlockDistance'] - assert curated_module_after['priorityExitShareThreshold'] == curated_module_before['priorityExitShareThreshold'] - assert len(curated_module_after.items()) == len(curated_module_before.items()) - assert len(curated_module_after.items()) == 13 + # additional checks to make sure no other fields were changed (if before state is available) + if curated_module_before is not None: + assert curated_module_after['id'] == curated_module_before['id'] + assert curated_module_after['stakingModuleAddress'] == curated_module_before['stakingModuleAddress'] + assert curated_module_after['stakeShareLimit'] == curated_module_before['stakeShareLimit'] + assert curated_module_after['status'] == curated_module_before['status'] + assert curated_module_after['name'] == curated_module_before['name'] + assert curated_module_after['lastDepositAt'] == curated_module_before['lastDepositAt'] + assert curated_module_after['lastDepositBlock'] == curated_module_before['lastDepositBlock'] + assert curated_module_after['exitedValidatorsCount'] == curated_module_before['exitedValidatorsCount'] + assert curated_module_after['maxDepositsPerBlock'] == curated_module_before['maxDepositsPerBlock'] + assert curated_module_after['minDepositBlockDistance'] == curated_module_before['minDepositBlockDistance'] + assert curated_module_after['priorityExitShareThreshold'] == curated_module_before['priorityExitShareThreshold'] + assert len(curated_module_after.items()) == len(curated_module_before.items()) + assert len(curated_module_after.items()) == 13 # Item 1.2 sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) @@ -1082,34 +1083,37 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE assert sdvt_module_after['name'] == SDVT_MODULE_NAME - # additional checks to make sure no other fields were changed - assert sdvt_module_after['id'] == sdvt_module_before['id'] - assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] - assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] - assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] - assert sdvt_module_after['status'] == sdvt_module_before['status'] - assert sdvt_module_after['name'] == sdvt_module_before['name'] - assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] - assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] - assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] - assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] - assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] - assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] - assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) - assert len(sdvt_module_after.items()) == 13 + # additional checks to make sure no other fields were changed (if before state is available) + if sdvt_module_before is not None: + assert sdvt_module_after['id'] == sdvt_module_before['id'] + assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] + assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] + assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] + assert sdvt_module_after['status'] == sdvt_module_before['status'] + assert sdvt_module_after['name'] == sdvt_module_before['name'] + assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] + assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] + assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] + assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] + assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] + assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] + assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) + assert len(sdvt_module_after.items()) == 13 # Item 1.3 a41_summary_after = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) assert a41_summary_after['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_AFTER assert a41_summary_after['depositableValidatorsCount'] == 0 - # additional checks to make sure no other fields were changed - assert a41_summary_after['targetValidatorsCount'] == a41_summary_before['targetValidatorsCount'] - assert a41_summary_after['stuckValidatorsCount'] == a41_summary_before['stuckValidatorsCount'] - assert a41_summary_after['refundedValidatorsCount'] == a41_summary_before['refundedValidatorsCount'] - assert a41_summary_after['stuckPenaltyEndTimestamp'] == a41_summary_before['stuckPenaltyEndTimestamp'] - assert a41_summary_after['totalExitedValidators'] == a41_summary_before['totalExitedValidators'] - assert a41_summary_after['totalDepositedValidators'] == a41_summary_before['totalDepositedValidators'] - assert len(a41_summary_after.items()) == 8 + assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" + # additional checks to make sure no other fields were changed (if before state is available) + if a41_summary_before is not None: + assert a41_summary_after['targetValidatorsCount'] == a41_summary_before['targetValidatorsCount'] + assert a41_summary_after['stuckValidatorsCount'] == a41_summary_before['stuckValidatorsCount'] + assert a41_summary_after['refundedValidatorsCount'] == a41_summary_before['refundedValidatorsCount'] + assert a41_summary_after['stuckPenaltyEndTimestamp'] == a41_summary_before['stuckPenaltyEndTimestamp'] + assert a41_summary_after['totalExitedValidators'] == a41_summary_before['totalExitedValidators'] + assert a41_summary_after['totalDepositedValidators'] == a41_summary_before['totalDepositedValidators'] + assert len(a41_summary_after.items()) == 8 # Items 1.4,1.5 trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() From 2c92aa8f899731585bfb0e43e78d799c8d276f40 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 2 Dec 2025 20:06:47 +0000 Subject: [PATCH 083/178] feat: add a41 soft limit reset (iii) --- scripts/vote_2025_12_10.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 9108abad3..d7a075df8 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -9,7 +9,7 @@ 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 III. Set A41 soft target validator limit to 0 -1.3. Set soft target validator limit for A41 (Node Operator ID = 32) in Curated Module (MODULE_ID = 1) to 0 +1.3. Set soft target validator limit for A41 (Node Operator ID = 32) in Curated Module (MODULE_ID = 1) to 0 in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 IV. Reset Easy Track TRP limit 1.4. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO @@ -261,7 +261,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.3. Set soft target validator limit for A41 (Node Operator ID = 32) in Curated Module (MODULE_ID = 1) to 0 + # 1.3. Set soft target validator limit for A41 (Node Operator ID = 32) in Curated Module (MODULE_ID = 1) to 0 in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE, NEW_A41_TARGET_LIMIT), From 7a7a6493d27575f88ba64aa6fe53fd9d02f50027 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 2 Dec 2025 20:35:13 +0000 Subject: [PATCH 084/178] fix: fix tests --- tests/acceptance/test_node_operators_registry.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/acceptance/test_node_operators_registry.py b/tests/acceptance/test_node_operators_registry.py index aea5d7069..794da4ab6 100644 --- a/tests/acceptance/test_node_operators_registry.py +++ b/tests/acceptance/test_node_operators_registry.py @@ -114,7 +114,7 @@ def test_nor_state(contract): assert node_operator["totalVettedValidators"] <= node_operator["totalAddedValidators"] node_operator_summary = contract.getNodeOperatorSummary(id) - exited_node_operators = [12, 1] # NO id 12 was added on vote 23-05-23, NO id 1 was added on vote 03-10-23 + exited_node_operators = [12, 1, 32] # NO id 12 was added on vote 23-05-23, NO id 1 was added on vote 03-10-23, NO id 32 was added on vote 10-12-25 assert node_operator_summary["targetLimitMode"] == (1 if id in exited_node_operators else 0) assert node_operator_summary["targetValidatorsCount"] == 0 # Can be more than 0 in regular protocol operations @@ -133,11 +133,13 @@ def test_nor_state(contract): assert node_operator["totalExitedValidators"] == node_operator_summary["totalExitedValidators"] assert node_operator["totalDepositedValidators"] == node_operator_summary["totalDepositedValidators"] - no_depositable_validators_count = ( - node_operator["totalVettedValidators"] - node_operator["totalDepositedValidators"] - ) - - assert node_operator_summary["depositableValidatorsCount"] == no_depositable_validators_count + if id in exited_node_operators: + assert node_operator_summary["depositableValidatorsCount"] == 0 + else: + no_depositable_validators_count = ( + node_operator["totalVettedValidators"] - node_operator["totalDepositedValidators"] + ) + assert node_operator_summary["depositableValidatorsCount"] == no_depositable_validators_count def _str_to_bytes32(s: str) -> str: return "0x{:0<64}".format(s.encode("utf-8").hex()) From 34c325a7e857e5ee78f696c0aa427af27cdda8bc Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Tue, 2 Dec 2025 22:49:10 +0000 Subject: [PATCH 085/178] chore: use develop branch for core tests --- .github/workflows/core_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 46762fb81..cfa5f1c69 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -36,7 +36,7 @@ jobs: - name: Run init script run: docker exec -e CORE_BRANCH tests-runner bash -c 'make init' env: - CORE_BRANCH: feat/vaults-scripts + CORE_BRANCH: develop - name: Run node run: docker exec -e ETH_RPC_URL --detach tests-runner bash -c 'NODE_PORT=8546 make node' From 77fd574b3f9883abdd82d720b4489fadc1e52002 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 3 Dec 2025 08:29:44 +0000 Subject: [PATCH 086/178] fix: desc --- scripts/vote_2025_12_10.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index d7a075df8..6417d2fa9 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -9,7 +9,7 @@ 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 III. Set A41 soft target validator limit to 0 -1.3. Set soft target validator limit for A41 (Node Operator ID = 32) in Curated Module (MODULE_ID = 1) to 0 in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +1.3. Set soft-mode target validators limit to 0 for Node operator A41 (ID = 32) in Curated Module (MODULE_ID = 1) in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 IV. Reset Easy Track TRP limit 1.4. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO @@ -261,7 +261,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.3. Set soft target validator limit for A41 (Node Operator ID = 32) in Curated Module (MODULE_ID = 1) to 0 in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.3. Set soft-mode target validators limit to 0 for Node operator A41 (ID = 32) in Curated Module (MODULE_ID = 1) in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE, NEW_A41_TARGET_LIMIT), From 8e31639a87b37251dd6952e749e7581fc5ddcaca Mon Sep 17 00:00:00 2001 From: dry914 Date: Wed, 3 Dec 2025 17:39:13 +0300 Subject: [PATCH 087/178] feat: addresses from V3 mainnet deployment --- configs/config_mainnet.py | 28 +++++----- scripts/upgrade_2025_12_10_mainnet_v3.py | 2 +- tests/regression/test_permissions.py | 4 +- tests/test_2025_12_10_mainnet_v3.py | 67 ++++++++++-------------- 4 files changed, 46 insertions(+), 55 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 5b25c9a8c..207a9251c 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -108,7 +108,7 @@ # LidoLocator LIDO_LOCATOR = "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" -LIDO_LOCATOR_IMPL = "0x26329a3D4cF2F89923b5c4C14a25A2485cD01aA2" # TODO fix after mainnet deployment +LIDO_LOCATOR_IMPL = "0x2f8779042EFaEd4c53db2Ce293eB6B3f7096C72d" # Other upgrade addresses LIDO_V2_UPGRADE_TEMPLATE = "0xa818fF9EC93122Bf9401ab4340C42De638CD600a" @@ -121,7 +121,7 @@ WITHDRAWAL_CREDENTIALS = "0x010000000000000000000000b9d7934878b5fb9610b3fe8a5e441e8fad7e293f" # Lido -LIDO_IMPL = "0xD0b9826e0EAf6dE91CE7A6783Cd6fd137ae422Ec" # TODO fix after mainnet deployment +LIDO_IMPL = "0x6ca84080381E43938476814be61B779A8bB6a600" # see Lido's proxy appId() LIDO_ARAGON_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" LIDO_MAX_STAKE_LIMIT_ETH = 150_000 @@ -201,7 +201,7 @@ EXIT_EVENTS_LOOKBACK_WINDOW_IN_SLOTS = 14 * 7200 # 14 days # OracleReportSanityChecker -ORACLE_REPORT_SANITY_CHECKER = "0x4c42eF565ED49aDcC459a53C3abDCa86617E2f63" # TODO fix after mainnet deployment +ORACLE_REPORT_SANITY_CHECKER = "0xf1647c86E6D7959f638DD9CE1d90e2F3C9503129" APPEARED_VALIDATORS_PER_DAY_LIMIT = 1800 EXITED_VALIDATORS_PER_DAY_LIMIT = 3600 ANNUAL_BALANCE_INCREASE_BP_LIMIT = 1000 # 10% @@ -216,7 +216,7 @@ CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 50 # Burner -BURNER = "0xD140f4f3C515E1a328F6804C5426d9e8b883ED50" # TODO fix after mainnet deployment +BURNER = "0xE76c52750019b80B43E36DF30bf4060EB73F573a" TOTAL_NON_COVER_SHARES_BURNT = 32145684728326685744 TOTAL_COVER_SHARES_BURNT = 0 @@ -239,7 +239,7 @@ # AccountingOracle # and its corresponding HashConsensus ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" -ACCOUNTING_ORACLE_IMPL = "0xb2295820F5286BE40c2da8102eB2cDB24aD608Be" # TODO fix after mainnet deployment +ACCOUNTING_ORACLE_IMPL = "0x1455B96780A93e08abFE41243Db92E2fCbb0141c" HASH_CONSENSUS_FOR_AO = "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288" AO_EPOCHS_PER_FRAME = 225 AO_FAST_LANE_LENGTH_SLOTS = 100 @@ -364,7 +364,7 @@ OBOL_LIDO_SPLIT_IMPL = "0x2fB59065F049e0D0E3180C6312FA0FeB5Bbf0FE3" # Ethereum Part for Optimism bridge -L1_TOKEN_RATE_NOTIFIER = "0x7c7419E1964188C6133051dF541cf2a525AA1038" # TODO fix after mainnet deployment +L1_TOKEN_RATE_NOTIFIER = "0x25e35855783bec3E49355a29e110f02Ed8b05ba9" L1_OPTIMISM_TOKEN_RATE_PUSHER = "0xd54c1c6413caac3477AC14b2a80D5398E3c32FfE" L1_OPTIMISM_TOKENS_BRIDGE = "0x76943C0D61395d8F2edF9060e1533529cAe05dE6" L1_OPTIMISM_TOKENS_BRIDGE_IMPL = "0x29C5c51A031165CE62F964966A6399b81165EFA4" @@ -526,17 +526,17 @@ # Lido V3 - stVaults ---------------------------------------------------------- # ----------------------------------------------------------------------------- -VAULT_HUB = "0xdcC04F506E24495E9F2599A7b214522647363669" # TODO fix after mainnet deployment +VAULT_HUB = "0x1d201BE093d847f6446530Efb0E8Fb426d176709" -ACCOUNTING = "0xc9158c756D2a510eaC85792C847798a30dE20D46" # TODO fix after mainnet deployment -ACCOUNTING_IMPL = "0x1bE2Ee7D32e3F9C4ecd5b6BfF69306ACb8Ab239e" # TODO fix after mainnet deployment +ACCOUNTING = "0x23ED611be0e1a820978875C0122F92260804cdDf" +ACCOUNTING_IMPL = "0xd43a3E984071F40d5d840f60708Af0e9526785df" -OPERATOR_GRID = "0x79e2685C1DD4756AC709a6CeE7C6cC960128B031" # TODO fix after mainnet deployment +OPERATOR_GRID = "0xC69685E89Cefc327b43B7234AC646451B27c544d" -LAZY_ORACLE = "0x4fA3c917BB8f8CD9d056C5DDF0a38bd1834c43F9" # TODO fix after mainnet deployment +LAZY_ORACLE = "0x5DB427080200c235F2Ae8Cd17A7be87921f7AD6c" -PREDEPOSIT_GUARANTEE = "0x7B49b203A100E326B84886dCC0e2c426f9b8cbBd" # TODO fix after mainnet deployment +PREDEPOSIT_GUARANTEE = "0xF4bF42c6D6A0E38825785048124DBAD6c9eaaac3" -VAULTS_ADAPTER = "0x8cDA09f41970A8f4416b6bA4696a2f09a6080c76" # TODO fix after mainnet deployment +VAULTS_ADAPTER = "0xe2DE6d2DefF15588a71849c0429101F8ca9FB14D" -GATE_SEAL_V3 = "0x9c2D30177DB12334998EB554f5d4E6dD44458167" # TODO fix after mainnet deployment +GATE_SEAL_V3 = "0x881dAd714679A6FeaA636446A0499101375A365c" diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index 9be9a557b..abb0a790c 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -46,7 +46,7 @@ # ============================== Addresses =================================== -OMNIBUS_CONTRACT = "0x7e2ef38FeDFEc1e768E55D63cb0273a726d0a318" # TODO replace with the actual omnibus contract address +OMNIBUS_CONTRACT = "0xa47Ca1d2029D8e735237ea4E74c607426d4aA07e" # ============================= Description ================================== diff --git a/tests/regression/test_permissions.py b/tests/regression/test_permissions.py index c144d7d3c..ed1436c59 100644 --- a/tests/regression/test_permissions.py +++ b/tests/regression/test_permissions.py @@ -509,7 +509,7 @@ def protocol_permissions(): "proxy_owner": contracts.agent, "roles": { "DEFAULT_ADMIN_ROLE": [contracts.agent], - "VAULT_MASTER_ROLE": [contracts.agent], + "VAULT_MASTER_ROLE": [], "REDEMPTION_MASTER_ROLE": [], "VALIDATOR_EXIT_ROLE": [VAULTS_ADAPTER], "BAD_DEBT_MASTER_ROLE": [VAULTS_ADAPTER], @@ -532,7 +532,7 @@ def protocol_permissions(): "proxy_owner": contracts.agent, "roles": { "DEFAULT_ADMIN_ROLE": [contracts.agent], - "REGISTRY_ROLE": [contracts.agent, EASYTRACK_EVMSCRIPT_EXECUTOR, VAULTS_ADAPTER], + "REGISTRY_ROLE": [EASYTRACK_EVMSCRIPT_EXECUTOR, VAULTS_ADAPTER], }, "role_preimages": { "REGISTRY_ROLE": "vaults.OperatorsGrid.Registry", diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index e45aab0ae..a3ac1b1a9 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -63,31 +63,33 @@ OLD_BURNER = "0xD15a672319Cf0352560eE76d9e89eAB0889046D3" LIDO_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" -# New Lido V3 addresses - TODO fix after mainnet deployment -VAULT_HUB = "0xdcC04F506E24495E9F2599A7b214522647363669" -ACCOUNTING = "0xc9158c756D2a510eaC85792C847798a30dE20D46" -ACCOUNTING_IMPL = "0x1bE2Ee7D32e3F9C4ecd5b6BfF69306ACb8Ab239e" -OPERATOR_GRID = "0x79e2685C1DD4756AC709a6CeE7C6cC960128B031" -LAZY_ORACLE = "0x4fA3c917BB8f8CD9d056C5DDF0a38bd1834c43F9" -PREDEPOSIT_GUARANTEE = "0x7B49b203A100E326B84886dCC0e2c426f9b8cbBd" -VAULTS_ADAPTER = "0x8cDA09f41970A8f4416b6bA4696a2f09a6080c76" -GATE_SEAL_V3 = "0x9c2D30177DB12334998EB554f5d4E6dD44458167" -LIDO_IMPL = "0xD0b9826e0EAf6dE91CE7A6783Cd6fd137ae422Ec" -UPGRADE_TEMPLATE = "0x2AE2847a77a1d24100be2163AfdC49B06DC808E4" -LIDO_LOCATOR_IMPL = "0x26329a3D4cF2F89923b5c4C14a25A2485cD01aA2" -ACCOUNTING_ORACLE_IMPL = "0xb2295820F5286BE40c2da8102eB2cDB24aD608Be" +# New Lido V3 addresses +VAULT_HUB = "0x1d201BE093d847f6446530Efb0E8Fb426d176709" +ACCOUNTING = "0x23ED611be0e1a820978875C0122F92260804cdDf" +ACCOUNTING_IMPL = "0xd43a3E984071F40d5d840f60708Af0e9526785df" +OPERATOR_GRID = "0xC69685E89Cefc327b43B7234AC646451B27c544d" +LAZY_ORACLE = "0x5DB427080200c235F2Ae8Cd17A7be87921f7AD6c" +PREDEPOSIT_GUARANTEE = "0xF4bF42c6D6A0E38825785048124DBAD6c9eaaac3" +VAULTS_ADAPTER = "0xe2DE6d2DefF15588a71849c0429101F8ca9FB14D" +GATE_SEAL_V3 = "0x881dAd714679A6FeaA636446A0499101375A365c" +LIDO_IMPL = "0x6ca84080381E43938476814be61B779A8bB6a600" +UPGRADE_TEMPLATE = "0x34E01ecFebd403370b0879C628f8A5319dDb8507" +LIDO_LOCATOR_IMPL = "0x2f8779042EFaEd4c53db2Ce293eB6B3f7096C72d" +ACCOUNTING_ORACLE_IMPL = "0x1455B96780A93e08abFE41243Db92E2fCbb0141c" RESEAL_MANAGER = "0x7914b5a1539b97Bd0bbd155757F25FD79A522d24" -BURNER = "0xD140f4f3C515E1a328F6804C5426d9e8b883ED50" - -ALTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0x25AB9D07356E8a3F95a5905f597c93CD8F31990b" -REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY = "0x9Ba3E4aDDe415943A831b97F9f7B8b842052b709" -REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0x663cE8Aa1ded537Dc319529e71DE6BAb2E7D0747" -SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY = "0x88D32aABa5B6A972D82E55D26B212eBeca07C983" -SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY = "0xf61601787E84d89c30911e5A48d9CA630eC47044" -SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY = "0xD60671a489948641954736A0e7272137b3A335CE" -FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY = "0x6D0F542591eF4f8ebA8c77a11dc3676D9E9F7e66" -UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY = "0xAEDB8D15197984D39152A2814e0BdCDAEED5462d" -UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY = "0x7DCf0746ff2F77A36ceC8E318b59dc8a8A5c066e" +BURNER = "0xE76c52750019b80B43E36DF30bf4060EB73F573a" + +ALTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0xa29173C7BCf39dA48D5E404146A652d7464aee14" +REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY = "0x194A46DA1947E98c9D79af13E06Cfbee0D8610cC" +REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0x5292A1284e4695B95C0840CF8ea25A818751C17F" +SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY = "0x93F1DEE4473Ee9F42c8257C201e33a6Da30E5d67" +SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY = "0x1dF50522A1D868C12bF71747Bb6F24A18Fe6d32C" +FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY = "0x6C968cD89CA358fbAf57B18e77a8973Fa869a6aA" +UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY = "0x8Bdc726a3147D8187820391D7c6F9F942606aEe6" +UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY = "0x5C3bDFa3E7f312d8cf72F56F2b797b026f6B471c" + +NEW_LIDO_VERSION = 3 +NEW_ACCOUNTING_ORACLE_VERSION = 4 UTC14 = 60 * 60 * 14 UTC23 = 60 * 60 * 23 @@ -95,7 +97,7 @@ EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 -EXPECTED_VOTE_EVENTS_COUNT = 10 +EXPECTED_VOTE_EVENTS_COUNT = 9 EXPECTED_DG_EVENTS_FROM_AGENT = 17 EXPECTED_DG_EVENTS_COUNT = 18 IPFS_DESCRIPTION_HASH = "bafkreic4xuaowfowt7faxnngnzynv7biuo7guv4s4jrngngjzzxyz3up2i" @@ -505,13 +507,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================= Before voting checks ======================== # ======================================================================= - # Steps 2-10: Add EasyTrack factories + # Steps 2-9: Add EasyTrack factories initial_factories = easy_track.getEVMScriptFactories() assert ALTER_TIERS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have ALTER_TIERS_IN_OPERATOR_GRID_FACTORY factory before vote" assert REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY factory before vote" assert REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY factory before vote" assert SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY factory before vote" - assert SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY not in initial_factories, "EasyTrack should not have SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY factory before vote" assert SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY not in initial_factories, "EasyTrack should not have SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY factory before vote" assert FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY not in initial_factories, "EasyTrack should not have FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY factory before vote" assert UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY factory before vote" @@ -536,13 +537,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g bad_debt_master_role = web3.keccak(text="vaults.VaultHub.BadDebtMasterRole") assert vault_hub.hasRole(bad_debt_master_role, VAULTS_ADAPTER), "Vault Hub should have BAD_DEBT_MASTER_ROLE on VAULTS_ADAPTER after upgrade" - # Steps 2-10: Add EasyTrack factories + # Steps 2-9: Add EasyTrack factories new_factories = easy_track.getEVMScriptFactories() assert ALTER_TIERS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have ALTER_TIERS_IN_OPERATOR_GRID_FACTORY factory after vote" assert REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY factory after vote" assert REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY factory after vote" assert SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY factory after vote" - assert SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY in new_factories, "EasyTrack should have SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY factory after vote" assert SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY in new_factories, "EasyTrack should have SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY factory after vote" assert FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY in new_factories, "EasyTrack should have FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY factory after vote" assert UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY factory after vote" @@ -631,15 +631,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g validate_evmscript_factory_added_event( event=vote_events[8], - p=EVMScriptFactoryAdded( - factory_addr=SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB_FACTORY, - permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "setLiabilitySharesTarget") - ), - emitted_by=easy_track, - ) - - validate_evmscript_factory_added_event( - event=vote_events[9], p=EVMScriptFactoryAdded( factory_addr=SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY, permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "socializeBadDebt") From 556e7d9e500aa3834183d6b3853cd388d7899804 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 4 Dec 2025 10:59:20 +0000 Subject: [PATCH 088/178] fix: removed Stonks factories --- scripts/vote_2025_12_10.py | 123 +++-------------- tests/test_2025_12_10.py | 274 +++---------------------------------- 2 files changed, 35 insertions(+), 362 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 6417d2fa9..ebee280ab 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -15,28 +15,16 @@ 1.4. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO 1.5. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months -V. Grant Easy Track EVM Script Executor permissions to add and remove recipients for stETH and stablecoin Stonks Easy Tracks -1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 -1.7 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 -1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 -1.9 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 - === NON-DG ITEMS === -VI. Add Easy Track factories for managing recipients of Stonks stETH and stablecoins top-up factories -2. Add AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 -3. Add RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 -4. Add AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 -5. Add RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 - -VII. Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance -6. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e -7. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca -8. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e -9. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 -10. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS - -VIII. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig -11. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 +V. Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance +2. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e +3. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca +4. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e +5. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 +6. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS + +VI. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig +7. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ @@ -53,7 +41,6 @@ from utils.dual_governance import submit_proposals from utils.agent import agent_forward from utils.permissions import encode_permission_revoke, encode_permission_grant_p -from utils.easy_track import add_evmscript_factory, create_permissions from utils.allowed_recipients_registry import ( unsafe_set_spent_amount, set_limit_parameters, @@ -75,20 +62,10 @@ class TokenLimit(NamedTuple): STABLECOINS_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" -STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY = "0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22" -STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY = "0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A" -STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0" - -STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY = "0x56bcff69e1d06e18C46B65C00D41B4ae82890184" -STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY = "0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD" -STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY = "0x3f0534CCcFb952470775C516DC2eff8396B8A368" - # ============================== Roles =================================== CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" -ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE = "ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE" -REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE = "REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE" # ============================== Tokens =================================== @@ -226,8 +203,6 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: staking_router = interface.StakingRouter(STAKING_ROUTER) stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) - stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) - stonks_stablecoins_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) dg_items = [ agent_forward([ @@ -279,83 +254,19 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: registry_address=ET_TRP_REGISTRY, ), ]), - agent_forward([ - # 1.6 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 - ( - stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR, - ) - ), - ]), - agent_forward([ - # 1.7 Grant ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 - ( - stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR, - ) - ), - ]), - agent_forward([ - # 1.8 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0 - ( - stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR, - ) - ), - ]), - agent_forward([ - # 1.9 Grant REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE to EVMScriptExecutor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Stonks Stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368 - ( - stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR, - ) - ), - ]), ] dg_call_script = submit_proposals([ - (dg_items, "Change Curated Module fees, raise SDVT stake share limit, reset Easy Track TRP limit and grant Stonks allowed recipients management permissions to Easy Track EVM Script Executor") + (dg_items, "Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, reset Easy Track TRP limit") ]) vote_desc_items, call_script_items = zip( ( - "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit, reset Easy Track TRP limit and grant Stonks allowed recipients management permissions to Easy Track EVM Script Executor", + "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, reset Easy Track TRP limit", dg_call_script[0] ), ( - "2. Add AddAllowedRecipient Factory 0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22 for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0", - add_evmscript_factory( - factory=STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_steth_allowed_recipients_registry, "addRecipient"), - ), - ), - ( - "3. Add RemoveAllowedRecipient Factory 0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A for Stonks stETH to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stETH AllowedRecipientsRegistry 0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0", - add_evmscript_factory( - factory=STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_steth_allowed_recipients_registry, "removeRecipient"), - ), - ), - ( - "4. Add AddAllowedRecipient Factory 0x56bcff69e1d06e18C46B65C00D41B4ae82890184 for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with addRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", - add_evmscript_factory( - factory=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "addRecipient"), - ), - ), - ( - "5. Add RemoveAllowedRecipient Factory 0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD for Stonks stablecoins to Easy Track 0xF0211b7660680B49De1A7E9f25C65660F0a13Fea with removeRecipient permission on Stonks stablecoins AllowedRecipientsRegistry 0x3f0534CCcFb952470775C516DC2eff8396B8A368", - add_evmscript_factory( - factory=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "removeRecipient"), - ), - ), - ( - "6. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", + "2. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), @@ -364,11 +275,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "7. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca", + "3. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca", (stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.addToken.encode_input(SUSDS_TOKEN)) ), ( - "8. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", + "4. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.revokeRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), @@ -377,7 +288,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ) ), ( - "9. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86", + "5. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86", encode_permission_revoke( target_app=FINANCE, permission_name=CREATE_PAYMENTS_ROLE, @@ -385,7 +296,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "10. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS", + "6. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS", encode_permission_grant_p( target_app=FINANCE, permission_name=CREATE_PAYMENTS_ROLE, @@ -394,7 +305,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ), ( - "11. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5", + "7. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5", make_matic_payout( target_address=LOL_MS, matic_in_wei=MATIC_FOR_TRANSFER, diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 0dc4cebbb..9bac27899 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -4,18 +4,12 @@ from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 from brownie.network.transaction import TransactionReceipt from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op -from utils.test.easy_track_helpers import ( - create_and_enact_payment_motion, - create_and_enact_add_recipient_motion, - create_and_enact_remove_recipient_motion, - check_add_and_remove_recipient_with_voting, -) +from utils.test.easy_track_helpers import create_and_enact_payment_motion from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id from utils.agent import agent_forward from utils.ipfs import get_lido_vote_cid_from_str -from utils.easy_track import create_permissions from utils.dual_governance import PROPOSAL_STATUS from utils.test.event_validators.allowed_tokens_registry import validate_add_token_event from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event @@ -45,10 +39,6 @@ validate_permission_grantp_event, validate_permission_revoke_event, ) -from utils.test.event_validators.easy_track import ( - validate_evmscript_factory_added_event, - EVMScriptFactoryAdded, -) from utils.test.event_validators.node_operators_registry import ( validate_target_validators_count_changed_event, TargetValidatorsCountChanged, @@ -97,17 +87,6 @@ class TokenLimit(NamedTuple): GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x49d1363016aA899bba09ae972a1BF200dDf8C55F" GAS_SUPPLY_STETH_SPENDABLE_BALANCE = 1_000 * 10**18 -STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY = "0x8b18e9b7c17c20Ae2f4F825429e9b5e788194E22" -STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY = "0x5F6Db5A060Ac5145Af3C5590a4E1eaB080A8143A" -STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x1a7cFA9EFB4D5BfFDE87B0FaEb1fC65d653868C0" -STONKS_STETH_TOPUP_FACTORY = "0x6e04aED774B7c89BB43721AcDD7D03C872a51B69" -STONKS_MS = "0xa02FC823cCE0D016bD7e17ac684c9abAb2d6D647" - -STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY = "0x56bcff69e1d06e18C46B65C00D41B4ae82890184" -STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY = "0x4C75070Aa6e7f89fd5Cb6Ce77544e9cB2AC585DD" -STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY = "0x3f0534CCcFb952470775C516DC2eff8396B8A368" -STONKS_STABLECOINS_TOPUP_FACTORY = "0x0d2aefA542aFa8d9D1Ec35376068B88042FEF5f6" - LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" @@ -118,8 +97,6 @@ class TokenLimit(NamedTuple): CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" -ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE = "ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE" -REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE = "REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE" # ============================== Constants =================================== @@ -159,9 +136,6 @@ class TokenLimit(NamedTuple): ALLOWED_TOKENS_BEFORE = 3 ALLOWED_TOKENS_AFTER = 4 -ET_FACTORIES_LEN_BEFORE = 33 -ET_FACTORIES_LEN_AFTER = ET_FACTORIES_LEN_BEFORE + 4 - A41_NO_ID = 32 NO_TARGET_LIMIT_SOFT_MODE_BEFORE = 0 NO_TARGET_LIMIT_SOFT_MODE_AFTER = 1 @@ -186,8 +160,8 @@ class TokenLimit(NamedTuple): # ============================== Voting =================================== EXPECTED_VOTE_ID = 194 EXPECTED_DG_PROPOSAL_ID = 6 -EXPECTED_VOTE_EVENTS_COUNT = 11 -EXPECTED_DG_EVENTS_COUNT = 9 +EXPECTED_VOTE_EVENTS_COUNT = 7 +EXPECTED_DG_EVENTS_COUNT = 5 IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" @@ -377,8 +351,6 @@ def amount_limits_after() -> List[Param]: def dual_governance_proposal_calls(): staking_router = interface.StakingRouter(STAKING_ROUTER) - stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) - stonks_stablecoins_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) dg_items = [ agent_forward([ @@ -425,38 +397,6 @@ def dual_governance_proposal_calls(): registry_address=ET_TRP_REGISTRY, ), ]), - agent_forward([ - ( - stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR, - ) - ), - ]), - agent_forward([ - ( - stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR, - ) - ), - ]), - agent_forward([ - ( - stonks_steth_allowed_recipients_registry.address, stonks_steth_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR, - ) - ), - ]), - agent_forward([ - ( - stonks_stablecoins_allowed_recipients_registry.address, stonks_stablecoins_allowed_recipients_registry.grantRole.encode_input( - convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR, - ) - ), - ]), ] # Convert each dg_item to the expected format @@ -486,9 +426,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) acl = interface.ACL(ACL) stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) - stonks_steth_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY) - stonks_stablecoins_allowed_recipients_registry = interface.AllowedRecipientRegistry(STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY) - easy_track = interface.EasyTrack(EASY_TRACK) curated_module = interface.NodeOperatorsRegistry(CURATED_MODULE) @@ -520,21 +457,13 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # Item 1 is DG - skipped here - # Items 2-5 - et_factories_before = easy_track.getEVMScriptFactories() - assert len(et_factories_before) == ET_FACTORIES_LEN_BEFORE - assert STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY not in et_factories_before - assert STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY not in et_factories_before - assert STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY not in et_factories_before - assert STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY not in et_factories_before - - # Items 6,8 + # Items 2,4 assert not stablecoins_allowed_tokens_registry.hasRole( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING ) - # Item 7 + # Item 3 assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() assert len(allowed_tokens_before) == ALLOWED_TOKENS_BEFORE @@ -542,7 +471,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert allowed_tokens_before[1] == USDT_TOKEN assert allowed_tokens_before[2] == USDC_TOKEN - # Items 9,10 + # Items 5,6 assert acl.getPermissionParamsLength( ET_EVM_SCRIPT_EXECUTOR, FINANCE, @@ -559,7 +488,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert op == amount_limits_before()[i].op.value assert val == amount_limits_before()[i].value - # Item 11 + # Item 7 matic_treasury_balance_before = matic_token.balanceOf(agent.address) assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE matic_labs_balance_before = matic_token.balanceOf(LOL_MS) @@ -579,21 +508,13 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # Item 1 is DG - skipped here - # Items 2-5 - et_factories_after = easy_track.getEVMScriptFactories() - assert len(et_factories_after) == ET_FACTORIES_LEN_AFTER - assert STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY in et_factories_after - assert STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY in et_factories_after - assert STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY in et_factories_after - assert STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY in et_factories_after - - # Items 6,8 + # Items 2,4 assert not stablecoins_allowed_tokens_registry.hasRole( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), VOTING ) - # Item 7 + # Item 3 assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() assert len(allowed_tokens_before) == ALLOWED_TOKENS_AFTER @@ -602,7 +523,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert allowed_tokens_before[2] == USDC_TOKEN assert allowed_tokens_before[3] == SUSDS_TOKEN - # Items 9,10 + # Items 5,6 assert acl.getPermissionParamsLength( ET_EVM_SCRIPT_EXECUTOR, FINANCE, @@ -683,7 +604,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit + 1], ) - # Item 11 + # Item 7 matic_treasury_balance_after = matic_token.balanceOf(agent.address) assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER matic_labs_balance_after = matic_token.balanceOf(LOL_MS) @@ -693,12 +614,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 - # scenario tests for new factories - chain.snapshot() - check_add_and_remove_recipient_with_voting(stonks_steth_allowed_recipients_registry, helpers, ldo_holder, voting) - check_add_and_remove_recipient_with_voting(stonks_stablecoins_allowed_recipients_registry, helpers, ldo_holder, voting) - chain.revert() - assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT if EXPECTED_DG_PROPOSAL_ID is not None: @@ -710,64 +625,32 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g proposal_id=EXPECTED_DG_PROPOSAL_ID, proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="Change Curated Module fees, raise SDVT stake share limit, reset Easy Track TRP limit and grant Stonks allowed recipients management permissions to Easy Track EVM Script Executor", + metadata="Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, reset Easy Track TRP limit", proposal_calls=dual_governance_proposal_calls, ) # validate all other voting events - validate_evmscript_factory_added_event( - event=vote_events[1], - p=EVMScriptFactoryAdded( - factory_addr=STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_steth_allowed_recipients_registry, "addRecipient"), - ), - emitted_by=EASY_TRACK, - ) - validate_evmscript_factory_added_event( - event=vote_events[2], - p=EVMScriptFactoryAdded( - factory_addr=STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_steth_allowed_recipients_registry, "removeRecipient"), - ), - emitted_by=EASY_TRACK, - ) - validate_evmscript_factory_added_event( - event=vote_events[3], - p=EVMScriptFactoryAdded( - factory_addr=STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "addRecipient"), - ), - emitted_by=EASY_TRACK, - ) - validate_evmscript_factory_added_event( - event=vote_events[4], - p=EVMScriptFactoryAdded( - factory_addr=STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, - permissions=create_permissions(stonks_stablecoins_allowed_recipients_registry, "removeRecipient"), - ), - emitted_by=EASY_TRACK, - ) validate_grant_role_event( - events=vote_events[5], + events=vote_events[1], role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), grant_to=VOTING, sender=VOTING, emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, ) validate_add_token_event( - event=vote_events[6], + event=vote_events[2], token=SUSDS_TOKEN, emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY ) validate_revoke_role_event( - events=vote_events[7], + events=vote_events[3], role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), revoke_from=VOTING, sender=VOTING, emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, ) validate_permission_revoke_event( - event=vote_events[8], + event=vote_events[4], p=Permission( app=FINANCE, entity=ET_EVM_SCRIPT_EXECUTOR, @@ -776,7 +659,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g emitted_by=ACL, ) validate_permission_grantp_event( - event=vote_events[9], + event=vote_events[5], p=Permission( app=FINANCE, entity=ET_EVM_SCRIPT_EXECUTOR, @@ -786,7 +669,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g emitted_by=ACL, ) validate_token_payout_event( - event=vote_events[10], + event=vote_events[6], p=Payout( token_addr=MATIC_TOKEN, from_addr=AGENT, @@ -937,24 +820,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP - # Items 1.6-1.9 - assert not stonks_steth_allowed_recipients_registry.hasRole( - convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR - ) - assert not stonks_stablecoins_allowed_recipients_registry.hasRole( - convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR - ) - assert not stonks_steth_allowed_recipients_registry.hasRole( - convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR - ) - assert not stonks_stablecoins_allowed_recipients_registry.hasRole( - convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR - ) - if details["status"] == PROPOSAL_STATUS["submitted"]: chain.sleep(timelock.getAfterSubmitDelay() + 1) @@ -1014,34 +879,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, emitted_by=ET_TRP_REGISTRY, ) - validate_grant_role_event( - events=dg_events[5], - role=web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE).hex(), - grant_to=ET_EVM_SCRIPT_EXECUTOR, - sender=AGENT, - emitted_by=STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY, - ) - validate_grant_role_event( - events=dg_events[6], - role=web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE).hex(), - grant_to=ET_EVM_SCRIPT_EXECUTOR, - sender=AGENT, - emitted_by=STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY, - ) - validate_grant_role_event( - events=dg_events[7], - role=web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE).hex(), - grant_to=ET_EVM_SCRIPT_EXECUTOR, - sender=AGENT, - emitted_by=STONKS_STETH_ALLOWED_RECIPIENTS_REGISTRY, - ) - validate_grant_role_event( - events=dg_events[8], - role=web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE).hex(), - grant_to=ET_EVM_SCRIPT_EXECUTOR, - sender=AGENT, - emitted_by=STONKS_STABLECOINS_ALLOWED_RECIPIENTS_REGISTRY, - ) # ========================================================================= # ==================== After DG proposal executed checks ================== @@ -1125,81 +962,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - # Items 1.6-1.9 - assert stonks_steth_allowed_recipients_registry.hasRole( - convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR - ) - assert stonks_stablecoins_allowed_recipients_registry.hasRole( - convert.to_uint(web3.keccak(text=ADD_RECIPIENT_TO_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR - ) - assert stonks_steth_allowed_recipients_registry.hasRole( - convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR - ) - assert stonks_stablecoins_allowed_recipients_registry.hasRole( - convert.to_uint(web3.keccak(text=REMOVE_RECIPIENT_FROM_ALLOWED_LIST_ROLE)), - ET_EVM_SCRIPT_EXECUTOR - ) - - - # scenario tests for new factories - chain.snapshot() - create_and_enact_add_recipient_motion( - easy_track, - STONKS_MS, - stonks_steth_allowed_recipients_registry, - STONKS_STETH_ADD_ALLOWED_RECIPIENT_FACTORY, - stranger, - "New recipient", - ldo_holder, - ) - create_and_enact_payment_motion( - easy_track, - STONKS_MS, - interface.AllowedRecipientRegistry(STONKS_STETH_TOPUP_FACTORY), - interface.Lido(STETH_TOKEN), - [stranger], - [10 * 10**18], - stranger, - ) - create_and_enact_remove_recipient_motion( - easy_track, - STONKS_MS, - stonks_steth_allowed_recipients_registry, - STONKS_STETH_REM_ALLOWED_RECIPIENT_FACTORY, - stranger, - ldo_holder, - ) - - create_and_enact_add_recipient_motion( - easy_track, - STONKS_MS, - stonks_stablecoins_allowed_recipients_registry, - STONKS_STABLECOINS_ADD_ALLOWED_RECIPIENT_FACTORY, - stranger, - "New recipient", - ldo_holder, - ) - create_and_enact_payment_motion( - easy_track, - STONKS_MS, - interface.AllowedRecipientRegistry(STONKS_STABLECOINS_TOPUP_FACTORY), - interface.ERC20(USDC_TOKEN), - [stranger], - [10 * 10**6], - stranger, - ) - create_and_enact_remove_recipient_motion( - easy_track, - STONKS_MS, - stonks_stablecoins_allowed_recipients_registry, - STONKS_STABLECOINS_REM_ALLOWED_RECIPIENT_FACTORY, - stranger, - ldo_holder, - ) - chain.revert() # scenraio test for TRP ET factory behavior after the vote trp_limit_test(stranger) From ff0d0f603dadc631b79307753a117e9f186c462e Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 4 Dec 2025 14:12:29 +0300 Subject: [PATCH 089/178] fix: multiple dg proposals --- utils/dual_governance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/dual_governance.py b/utils/dual_governance.py index f621d6747..91307e874 100644 --- a/utils/dual_governance.py +++ b/utils/dual_governance.py @@ -59,7 +59,8 @@ def process_proposals(proposal_ids: Sequence[int]): submitted_proposals = [] scheduled_proposals = [] - for proposal_id in proposals_to_be_processed: + copy_proposals_to_be_processed = proposals_to_be_processed.copy() + for proposal_id in copy_proposals_to_be_processed: (_, _, _, _, proposal_status) = contracts.emergency_protected_timelock.getProposalDetails(proposal_id) if proposal_status == PROPOSAL_STATUS["submitted"]: submitted_proposals.append(proposal_id) From b7f184ce2b3d381e47309197cfa9368e31f261e8 Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 4 Dec 2025 14:46:54 +0300 Subject: [PATCH 090/178] tests: add new test cases --- scripts/upgrade_2025_12_10_mainnet_v3.py | 3 +- tests/test_2025_12_10.py | 86 ++-- tests/test_2025_12_10_mainnet_v3.py | 550 ++++++++++++----------- 3 files changed, 354 insertions(+), 285 deletions(-) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index abb0a790c..4275f6690 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -29,8 +29,7 @@ 6. Add SetJailStatusInOperatorGrid factory to Easy Track (permissions: vaultsAdapter, setVaultJailStatus) 7. Add UpdateVaultsFeesInOperatorGrid factory to Easy Track (permissions: vaultsAdapter, updateVaultFees) 8. Add ForceValidatorExitsInVaultHub factory to Easy Track (permissions: vaultsAdapter, forceValidatorExit) -9. Add SetLiabilitySharesTargetInVaultHub factory to Easy Track (permissions: vaultsAdapter, setLiabilitySharesTarget) -10. Add SocializeBadDebtInVaultHub factory to Easy Track (permissions: vaultsAdapter, socializeBadDebt) +9. Add SocializeBadDebtInVaultHub factory to Easy Track (permissions: vaultsAdapter, socializeBadDebt) # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 9bac27899..f5e54f44a 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -157,14 +157,6 @@ class TokenLimit(NamedTuple): DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" -# ============================== Voting =================================== -EXPECTED_VOTE_ID = 194 -EXPECTED_DG_PROPOSAL_ID = 6 -EXPECTED_VOTE_EVENTS_COUNT = 7 -EXPECTED_DG_EVENTS_COUNT = 5 -IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" - - # ============================== Finance Limits =================================== AMOUNT_LIMITS_LEN_BEFORE = 19 def amount_limits_before() -> List[Param]: @@ -412,7 +404,18 @@ def dual_governance_proposal_calls(): return proposal_calls -def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): +def enact_and_test_voting( + helpers, + accounts, + ldo_holder, + vote_ids_from_env, + stranger, + dual_governance_proposal_calls, + EXPECTED_VOTE_ID, + EXPECTED_DG_PROPOSAL_ID, +): + EXPECTED_VOTE_EVENTS_COUNT = 7 + IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" # ======================================================================= # ========================= Arrange variables =========================== @@ -420,7 +423,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g voting = interface.Voting(VOTING) agent = interface.Agent(AGENT) timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) - dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) matic_token = interface.ERC20(MATIC_TOKEN) staking_router = interface.StakingRouter(STAKING_ROUTER) et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) @@ -428,7 +430,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) curated_module = interface.NodeOperatorsRegistry(CURATED_MODULE) - # ========================================================================= # ======================== Identify or Create vote ======================== # ========================================================================= @@ -445,7 +446,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g onchain_script = voting.getVote(vote_id)["script"] assert onchain_script == encode_call_script(call_script_items) - # ========================================================================= # ============================= Execute Vote ============================== # ========================================================================= @@ -494,14 +494,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g matic_labs_balance_before = matic_token.balanceOf(LOL_MS) assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE - assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) display_voting_events(vote_tx) vote_events = group_voting_events_from_receipt(vote_tx) - # ======================================================================= # ========================= After voting checks ========================= # ======================================================================= @@ -575,7 +573,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert not acl.hasPermission["address,address,bytes32,uint[]"]( ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit + 1], - ) + ) assert acl.hasPermission["address,address,bytes32,uint[]"]( ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), @@ -584,7 +582,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert not acl.hasPermission["address,address,bytes32,uint[]"]( ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit + 1], - ) + ) assert acl.hasPermission["address,address,bytes32,uint[]"]( ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), @@ -593,7 +591,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert not acl.hasPermission["address,address,bytes32,uint[]"]( ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit + 1], - ) + ) assert acl.hasPermission["address,address,bytes32,uint[]"]( ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), @@ -602,7 +600,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert not acl.hasPermission["address,address,bytes32,uint[]"]( ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit + 1], - ) + ) # Item 7 matic_treasury_balance_after = matic_token.balanceOf(agent.address) @@ -729,7 +727,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g interface.ERC20(SUSDS_TOKEN), [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], [1 * 10**18], - stranger, + stranger, ) chain.revert() @@ -768,7 +766,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g interface.ERC20(WSTETH_TOKEN), [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], [1 * 10**18], - stranger, + stranger, ) chain.revert() @@ -776,6 +774,20 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g usds_wrap_happy_path(stranger) +def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): + EXPECTED_DG_EVENTS_COUNT = 5 + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + voting = interface.Voting(VOTING) + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + staking_router = interface.StakingRouter(STAKING_ROUTER) + et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) + curated_module = interface.NodeOperatorsRegistry(CURATED_MODULE) + if EXPECTED_DG_PROPOSAL_ID is not None: details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) if details["status"] != PROPOSAL_STATUS["executed"]: @@ -820,7 +832,6 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP - if details["status"] == PROPOSAL_STATUS["submitted"]: chain.sleep(timelock.getAfterSubmitDelay() + 1) dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) @@ -962,10 +973,29 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - # scenraio test for TRP ET factory behavior after the vote trp_limit_test(stranger) +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): + EXPECTED_VOTE_ID = 194 + EXPECTED_DG_PROPOSAL_ID = 6 + + enact_and_test_voting( + helpers, + accounts, + ldo_holder, + vote_ids_from_env, + stranger, + dual_governance_proposal_calls, + EXPECTED_VOTE_ID, + EXPECTED_DG_PROPOSAL_ID, + ) + + enact_and_test_dg( + stranger, + EXPECTED_DG_PROPOSAL_ID, + ) + def trp_limit_test(stranger): @@ -988,7 +1018,7 @@ def trp_limit_test(stranger): [to_spend + 1], stranger, ) - + # spend all in several transfers recipients = [] amounts = [] @@ -1039,7 +1069,7 @@ def et_limit_test(stranger, token, max_spend_at_once, to_spend, TRUSTED_CALLER, [to_spend + 1], stranger, ) - + # spend all in several transfers recipients = [] amounts = [] @@ -1098,7 +1128,7 @@ def finance_limit_test(stranger, token, to_spend, decimals, TRUSTED_CALLER, TOP_ [to_spend + 1], stranger, ) - + # spend the allowed balance create_and_enact_payment_motion( easy_track, @@ -1116,13 +1146,13 @@ def finance_limit_test(stranger, token, to_spend, decimals, TRUSTED_CALLER, TOP_ def usds_wrap_happy_path(stranger): USDC_FOR_TRANSFER = 1000 USDS_TOKEN = "0xdC035D45d973E3EC169d2276DDab16f1e407384F" - + easy_track = interface.EasyTrack(EASY_TRACK) - usdc = interface.Usdc(USDC_TOKEN) + usdc = interface.Usdc(USDC_TOKEN) psmVariant1Actions = interface.PSMVariant1Actions(PSM_VARIANT1_ACTIONS) usds_token = interface.Usds(USDS_TOKEN) susds_token = interface.Susds(SUSDS_TOKEN) - + eoa = accounts[0] chain.snapshot() diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/test_2025_12_10_mainnet_v3.py index 382e3d78d..fec0446b2 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/test_2025_12_10_mainnet_v3.py @@ -95,13 +95,6 @@ UTC23 = 60 * 60 * 23 SLASHING_RESERVE_SHIFT = 8192 -EXPECTED_VOTE_ID = 194 -EXPECTED_DG_PROPOSAL_ID = 6 -EXPECTED_VOTE_EVENTS_COUNT = 9 -EXPECTED_DG_EVENTS_FROM_AGENT = 17 -EXPECTED_DG_EVENTS_COUNT = 18 -IPFS_DESCRIPTION_HASH = "bafkreic4xuaowfowt7faxnngnzynv7biuo7guv4s4jrngngjzzxyz3up2i" - # ============================================================================ # ============================== Helper functions ============================ @@ -453,43 +446,41 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): return proposal_calls -def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): +def enact_and_test_voting( + helpers, + accounts, + ldo_holder, + vote_ids_from_env, + dual_governance_proposal_calls, + expected_vote_id, + expected_dg_proposal_id, +): + """ + Submit, enact and test the voting proposal. + Includes all before/after voting checks and event validation. + """ + EXPECTED_VOTE_EVENTS_COUNT = 9 + IPFS_DESCRIPTION_HASH = "bafkreic4xuaowfowt7faxnngnzynv7biuo7guv4s4jrngngjzzxyz3up2i" # ======================================================================= # ========================= Arrange variables =========================== # ======================================================================= voting = interface.Voting(VOTING) - agent = interface.Agent(AGENT) timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) - dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) easy_track = interface.EasyTrack(EASYTRACK) - kernel = interface.Kernel(ARAGON_KERNEL) - acl = interface.ACL(ACL) vault_hub = interface.VaultHub(VAULT_HUB) operator_grid = interface.OperatorGrid(OPERATOR_GRID) - predeposit_guarantee = interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) - burner = interface.Burner(BURNER) - - lido_locator_proxy = interface.OssifiableProxy(LIDO_LOCATOR) - accounting_oracle_proxy = interface.OssifiableProxy(ACCOUNTING_ORACLE) - staking_router = interface.StakingRouter(STAKING_ROUTER) - old_burner = interface.Burner(OLD_BURNER) - oracle_daemon_config = interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) - - # Save original implementations for comparison - locator_impl_before = get_ossifiable_proxy_impl(LIDO_LOCATOR) - accounting_oracle_impl_before = get_ossifiable_proxy_impl(ACCOUNTING_ORACLE) # ========================================================================= # ======================== Identify or Create vote ======================== # ========================================================================= if vote_ids_from_env: vote_id = vote_ids_from_env[0] - if EXPECTED_VOTE_ID is not None: - assert vote_id == EXPECTED_VOTE_ID - elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: - vote_id = EXPECTED_VOTE_ID + if expected_vote_id is not None: + assert vote_id == expected_vote_id + elif expected_vote_id is not None and voting.votesLength() > expected_vote_id: + vote_id = expected_vote_id else: vote_id, _ = start_vote({"from": ldo_holder}, silent=True) @@ -552,12 +543,12 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT - if EXPECTED_DG_PROPOSAL_ID is not None: - assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() + if expected_dg_proposal_id is not None: + assert expected_dg_proposal_id == timelock.getProposalsCount() validate_dual_governance_submit_event( vote_events[0], - proposal_id=EXPECTED_DG_PROPOSAL_ID, + proposal_id=expected_dg_proposal_id, proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, metadata="TODO DG proposal description", @@ -638,251 +629,300 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g ) - if EXPECTED_DG_PROPOSAL_ID is not None: - report_rewards_minted_role = web3.keccak(text="REPORT_REWARDS_MINTED_ROLE") - request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") - config_manager_role = web3.keccak(text="CONFIG_MANAGER_ROLE") - app_manager_role = web3.keccak(text="APP_MANAGER_ROLE") - - details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) - if details["status"] != PROPOSAL_STATUS["executed"]: - # ========================================================================= - # ================== DG before proposal executed checks =================== - # ========================================================================= - - # Step 1.3: Check Lido Locator implementation initial state - assert locator_impl_before != LIDO_LOCATOR_IMPL, "Locator implementation should be different before upgrade" - - # Step 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT - assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE before upgrade" - - # Step 1.5. Set Lido implementation in Kernel - assert not kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be different before upgrade" - - # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido - assert old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Lido before upgrade" - - # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module - assert old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Curated staking module before upgrade" - - # Step 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT - assert old_burner.hasRole(request_burn_shares_role, SIMPLE_DVT), "Old Burner should have REQUEST_BURN_SHARES_ROLE on SimpleDVT before upgrade" - - # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting - assert old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting before upgrade" - - # Step 1.11: Check Accounting Oracle implementation initial state - assert accounting_oracle_impl_before != ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be different before upgrade" - - # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido - assert staking_router.hasRole(report_rewards_minted_role, STETH), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Lido before upgrade" - - # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting - assert not staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Accounting before upgrade" - - # Step 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent - assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent before upgrade" - - # Step 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig - try: - oracle_daemon_config.get('SLASHING_RESERVE_WE_RIGHT_SHIFT') - assert False, "SLASHING_RESERVE_WE_RIGHT_SHIFT should not exist before vote" - except Exception: - pass # Expected to fail - - # Step 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig - try: - oracle_daemon_config.get('SLASHING_RESERVE_WE_LEFT_SHIFT') - assert False, "SLASHING_RESERVE_WE_LEFT_SHIFT should not exist before vote" - except Exception: - pass # Expected to fail - - if details["status"] == PROPOSAL_STATUS["submitted"]: - chain.sleep(timelock.getAfterSubmitDelay() + 1) - dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - - if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: - chain.sleep(timelock.getAfterScheduleDelay() + 1) - - wait_for_target_time_to_satisfy_time_constrains() - - dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - display_dg_events(dg_tx) - dg_events = group_dg_events_from_receipt( - dg_tx, - timelock=EMERGENCY_PROTECTED_TIMELOCK, - admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - ) - assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_FROM_AGENT - assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT - - # === DG EXECUTION EVENTS VALIDATION === - - # 1.2. Call V3Template.startUpgrade - validate_upgrade_started_event(dg_events[1]) - - # 1.3. Lido Locator upgrade events - validate_proxy_upgrade_event(dg_events[2], LIDO_LOCATOR_IMPL, emitted_by=lido_locator_proxy) - - # 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT - validate_aragon_grant_permission_event( - dg_events[3], - entity=AGENT, - app=ARAGON_KERNEL, - role=app_manager_role.hex(), - emitted_by=ACL, - ) - - # 1.5. Set Lido implementation in Kernel - validate_set_app_event( - dg_events[4], - app_id=LIDO_APP_ID, - app=LIDO_IMPL, - emitted_by=ARAGON_KERNEL, - ) - - # 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT - validate_aragon_revoke_permission_event( - dg_events[5], - entity=AGENT, - app=ARAGON_KERNEL, - role=app_manager_role.hex(), - emitted_by=ACL, - ) - - # 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido - validate_revoke_role_event( - dg_events[6], - role=request_burn_shares_role.hex(), - revoke_from=STETH, - sender=AGENT, - emitted_by=old_burner, - ) - - # 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module - validate_revoke_role_event( - dg_events[7], - role=request_burn_shares_role.hex(), - revoke_from=NODE_OPERATORS_REGISTRY, - sender=AGENT, - emitted_by=old_burner, - ) - - # 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT - validate_revoke_role_event( - dg_events[8], - role=request_burn_shares_role.hex(), - revoke_from=SIMPLE_DVT, - sender=AGENT, - emitted_by=old_burner, - ) - - # 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting - validate_revoke_role_event( - dg_events[9], - role=request_burn_shares_role.hex(), - revoke_from=CSM_ACCOUNTING, - sender=AGENT, - emitted_by=old_burner, - ) - - # 1.11. Accounting Oracle upgrade events - validate_proxy_upgrade_event(dg_events[10], ACCOUNTING_ORACLE_IMPL, emitted_by=accounting_oracle_proxy) - - # 1.12. Revoke Staking Router REPORT_REWARDS_MINTED_ROLE from the Lido - validate_revoke_role_event( - dg_events[11], - role=report_rewards_minted_role.hex(), - revoke_from=STETH, - sender=AGENT, - emitted_by=staking_router, - ) - - # 1.13. Grant Staking Router REPORT_REWARDS_MINTED_ROLE to Accounting - validate_grant_role_event( - dg_events[12], - role=report_rewards_minted_role.hex(), - grant_to=ACCOUNTING, - sender=AGENT, - emitted_by=staking_router, - ) - - # 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent - validate_grant_role_event( - dg_events[13], - role=config_manager_role.hex(), - grant_to=AGENT, - sender=AGENT, - emitted_by=oracle_daemon_config, - ) - - # 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig - validate_config_value_set_event( - dg_events[14], - key='SLASHING_RESERVE_WE_RIGHT_SHIFT', - value=SLASHING_RESERVE_SHIFT, - emitted_by=oracle_daemon_config, - ) - - # 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig - validate_config_value_set_event( - dg_events[15], - key='SLASHING_RESERVE_WE_LEFT_SHIFT', - value=SLASHING_RESERVE_SHIFT, - emitted_by=oracle_daemon_config, - ) - - # 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent - validate_revoke_role_event( - dg_events[16], - role=config_manager_role.hex(), - revoke_from=AGENT, - sender=AGENT, - emitted_by=oracle_daemon_config, - ) - - # 1.18. Call V3Template.finishUpgrade - validate_upgrade_finished_event(dg_events[17]) +def enact_and_test_dg(stranger, expected_dg_proposal_id): + """ + Enact and test the dual governance proposal. + Includes all before/after DG checks and event validation. + """ + if expected_dg_proposal_id is None: + return + + EXPECTED_DG_EVENTS_FROM_AGENT = 17 + EXPECTED_DG_EVENTS_COUNT = 18 + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + kernel = interface.Kernel(ARAGON_KERNEL) + acl = interface.ACL(ACL) + + lido_locator_proxy = interface.OssifiableProxy(LIDO_LOCATOR) + accounting_oracle_proxy = interface.OssifiableProxy(ACCOUNTING_ORACLE) + staking_router = interface.StakingRouter(STAKING_ROUTER) + old_burner = interface.Burner(OLD_BURNER) + oracle_daemon_config = interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) + + # Save original implementations for comparison + locator_impl_before = get_ossifiable_proxy_impl(LIDO_LOCATOR) + accounting_oracle_impl_before = get_ossifiable_proxy_impl(ACCOUNTING_ORACLE) + + report_rewards_minted_role = web3.keccak(text="REPORT_REWARDS_MINTED_ROLE") + request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") + config_manager_role = web3.keccak(text="CONFIG_MANAGER_ROLE") + app_manager_role = web3.keccak(text="APP_MANAGER_ROLE") + + details = timelock.getProposalDetails(expected_dg_proposal_id) + if details["status"] != PROPOSAL_STATUS["executed"]: # ========================================================================= - # ==================== After DG proposal executed checks ================== + # ================== DG before proposal executed checks =================== # ========================================================================= - # Step 1.3: Validate Lido Locator implementation was updated - assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" + # Step 1.3: Check Lido Locator implementation initial state + assert locator_impl_before != LIDO_LOCATOR_IMPL, "Locator implementation should be different before upgrade" - # Step 1.5. Set Lido implementation in Kernel - assert kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be updated to the new value" + # Step 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT + assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE before upgrade" - # Step 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT - assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE after upgrade" + # Step 1.5. Set Lido implementation in Kernel + assert not kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be different before upgrade" # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido - assert not old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Lido after upgrade" + assert old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Lido before upgrade" # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module - assert not old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Curated staking module after upgrade" + assert old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Curated staking module before upgrade" # Step 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT - assert not old_burner.hasRole(request_burn_shares_role, SIMPLE_DVT), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on SimpleDVT after upgrade" + assert old_burner.hasRole(request_burn_shares_role, SIMPLE_DVT), "Old Burner should have REQUEST_BURN_SHARES_ROLE on SimpleDVT before upgrade" # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting - assert not old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting after upgrade" + assert old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting before upgrade" - # Step 1.11: Validate Accounting Oracle implementation was updated - assert get_ossifiable_proxy_impl(accounting_oracle_proxy) == ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be updated to the new value" + # Step 1.11: Check Accounting Oracle implementation initial state + assert accounting_oracle_impl_before != ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be different before upgrade" # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido - assert not staking_router.hasRole(report_rewards_minted_role, STETH), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Lido after upgrade" + assert staking_router.hasRole(report_rewards_minted_role, STETH), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Lido before upgrade" # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting - assert staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Accounting after upgrade" + assert not staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Accounting before upgrade" + + # Step 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent + assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent before upgrade" # Step 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig - assert convert.to_uint(oracle_daemon_config.get("SLASHING_RESERVE_WE_RIGHT_SHIFT")) == SLASHING_RESERVE_SHIFT, "OracleDaemonConfig should have SLASHING_RESERVE_WE_RIGHT_SHIFT set to 0x2000 after upgrade" + try: + oracle_daemon_config.get('SLASHING_RESERVE_WE_RIGHT_SHIFT') + assert False, "SLASHING_RESERVE_WE_RIGHT_SHIFT should not exist before vote" + except Exception: + pass # Expected to fail # Step 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig - assert convert.to_uint(oracle_daemon_config.get("SLASHING_RESERVE_WE_LEFT_SHIFT")) == SLASHING_RESERVE_SHIFT, "OracleDaemonConfig should have SLASHING_RESERVE_WE_LEFT_SHIFT set to 0x2000 after upgrade" + try: + oracle_daemon_config.get('SLASHING_RESERVE_WE_LEFT_SHIFT') + assert False, "SLASHING_RESERVE_WE_LEFT_SHIFT should not exist before vote" + except Exception: + pass # Expected to fail + + if details["status"] == PROPOSAL_STATUS["submitted"]: + chain.sleep(timelock.getAfterSubmitDelay() + 1) + dual_governance.scheduleProposal(expected_dg_proposal_id, {"from": stranger}) + + if timelock.getProposalDetails(expected_dg_proposal_id)["status"] == PROPOSAL_STATUS["scheduled"]: + chain.sleep(timelock.getAfterScheduleDelay() + 1) + + wait_for_target_time_to_satisfy_time_constrains() + + dg_tx: TransactionReceipt = timelock.execute(expected_dg_proposal_id, {"from": stranger}) + display_dg_events(dg_tx) + dg_events = group_dg_events_from_receipt( + dg_tx, + timelock=EMERGENCY_PROTECTED_TIMELOCK, + admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + ) + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_FROM_AGENT + assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + + # === DG EXECUTION EVENTS VALIDATION === + + # 1.2. Call V3Template.startUpgrade + validate_upgrade_started_event(dg_events[1]) - # Step 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent - assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent after upgrade" + # 1.3. Lido Locator upgrade events + validate_proxy_upgrade_event(dg_events[2], LIDO_LOCATOR_IMPL, emitted_by=lido_locator_proxy) + + # 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT + validate_aragon_grant_permission_event( + dg_events[3], + entity=AGENT, + app=ARAGON_KERNEL, + role=app_manager_role.hex(), + emitted_by=ACL, + ) + + # 1.5. Set Lido implementation in Kernel + validate_set_app_event( + dg_events[4], + app_id=LIDO_APP_ID, + app=LIDO_IMPL, + emitted_by=ARAGON_KERNEL, + ) + + # 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT + validate_aragon_revoke_permission_event( + dg_events[5], + entity=AGENT, + app=ARAGON_KERNEL, + role=app_manager_role.hex(), + emitted_by=ACL, + ) + + # 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + validate_revoke_role_event( + dg_events[6], + role=request_burn_shares_role.hex(), + revoke_from=STETH, + sender=AGENT, + emitted_by=old_burner, + ) + + # 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + validate_revoke_role_event( + dg_events[7], + role=request_burn_shares_role.hex(), + revoke_from=NODE_OPERATORS_REGISTRY, + sender=AGENT, + emitted_by=old_burner, + ) + + # 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + validate_revoke_role_event( + dg_events[8], + role=request_burn_shares_role.hex(), + revoke_from=SIMPLE_DVT, + sender=AGENT, + emitted_by=old_burner, + ) + + # 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + validate_revoke_role_event( + dg_events[9], + role=request_burn_shares_role.hex(), + revoke_from=CSM_ACCOUNTING, + sender=AGENT, + emitted_by=old_burner, + ) + + # 1.11. Accounting Oracle upgrade events + validate_proxy_upgrade_event(dg_events[10], ACCOUNTING_ORACLE_IMPL, emitted_by=accounting_oracle_proxy) + + # 1.12. Revoke Staking Router REPORT_REWARDS_MINTED_ROLE from the Lido + validate_revoke_role_event( + dg_events[11], + role=report_rewards_minted_role.hex(), + revoke_from=STETH, + sender=AGENT, + emitted_by=staking_router, + ) + + # 1.13. Grant Staking Router REPORT_REWARDS_MINTED_ROLE to Accounting + validate_grant_role_event( + dg_events[12], + role=report_rewards_minted_role.hex(), + grant_to=ACCOUNTING, + sender=AGENT, + emitted_by=staking_router, + ) + + # 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent + validate_grant_role_event( + dg_events[13], + role=config_manager_role.hex(), + grant_to=AGENT, + sender=AGENT, + emitted_by=oracle_daemon_config, + ) + + # 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + validate_config_value_set_event( + dg_events[14], + key='SLASHING_RESERVE_WE_RIGHT_SHIFT', + value=SLASHING_RESERVE_SHIFT, + emitted_by=oracle_daemon_config, + ) + + # 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + validate_config_value_set_event( + dg_events[15], + key='SLASHING_RESERVE_WE_LEFT_SHIFT', + value=SLASHING_RESERVE_SHIFT, + emitted_by=oracle_daemon_config, + ) + + # 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent + validate_revoke_role_event( + dg_events[16], + role=config_manager_role.hex(), + revoke_from=AGENT, + sender=AGENT, + emitted_by=oracle_daemon_config, + ) + + # 1.18. Call V3Template.finishUpgrade + validate_upgrade_finished_event(dg_events[17]) + + # ========================================================================= + # ==================== After DG proposal executed checks ================== + # ========================================================================= + + # Step 1.3: Validate Lido Locator implementation was updated + assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" + + # Step 1.5. Set Lido implementation in Kernel + assert kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be updated to the new value" + + # Step 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT + assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE after upgrade" + + # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + assert not old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Lido after upgrade" + + # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + assert not old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Curated staking module after upgrade" + + # Step 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + assert not old_burner.hasRole(request_burn_shares_role, SIMPLE_DVT), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on SimpleDVT after upgrade" + + # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + assert not old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting after upgrade" + + # Step 1.11: Validate Accounting Oracle implementation was updated + assert get_ossifiable_proxy_impl(accounting_oracle_proxy) == ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be updated to the new value" + + # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido + assert not staking_router.hasRole(report_rewards_minted_role, STETH), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Lido after upgrade" + + # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting + assert staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Accounting after upgrade" + + # Step 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + assert convert.to_uint(oracle_daemon_config.get("SLASHING_RESERVE_WE_RIGHT_SHIFT")) == SLASHING_RESERVE_SHIFT, "OracleDaemonConfig should have SLASHING_RESERVE_WE_RIGHT_SHIFT set to 0x2000 after upgrade" + + # Step 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + assert convert.to_uint(oracle_daemon_config.get("SLASHING_RESERVE_WE_LEFT_SHIFT")) == SLASHING_RESERVE_SHIFT, "OracleDaemonConfig should have SLASHING_RESERVE_WE_LEFT_SHIFT set to 0x2000 after upgrade" + + # Step 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent + assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent after upgrade" + + +def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): + EXPECTED_VOTE_ID = 194 + EXPECTED_DG_PROPOSAL_ID = 6 + + enact_and_test_voting( + helpers, + accounts, + ldo_holder, + vote_ids_from_env, + dual_governance_proposal_calls, + expected_vote_id=EXPECTED_VOTE_ID, + expected_dg_proposal_id=EXPECTED_DG_PROPOSAL_ID, + ) + + enact_and_test_dg( + stranger, + expected_dg_proposal_id=EXPECTED_DG_PROPOSAL_ID, + ) From 41804cf1e17d6a3790830b454cbc76125b08c57e Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 4 Dec 2025 13:35:03 +0000 Subject: [PATCH 091/178] fix: shared testing --- tests/test_2025_12_10.py | 1291 +---------------- ..._v3.py => utils_test_2025_12_10_lidov3.py} | 26 +- tests/utils_test_2025_12_10_operations.py | 1256 ++++++++++++++++ 3 files changed, 1309 insertions(+), 1264 deletions(-) rename tests/{test_2025_12_10_mainnet_v3.py => utils_test_2025_12_10_lidov3.py} (98%) create mode 100644 tests/utils_test_2025_12_10_operations.py diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index f5e54f44a..791b2afdb 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -1,1279 +1,92 @@ import pytest -from typing import List, NamedTuple +import brownie +import tests.utils_test_2025_12_10_lidov3 as lidov3 +import tests.utils_test_2025_12_10_operations as ops -from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 -from brownie.network.transaction import TransactionReceipt -from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op -from utils.test.easy_track_helpers import create_and_enact_payment_motion -from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem -from utils.evm_script import encode_call_script -from utils.voting import find_metadata_by_vote_id -from utils.agent import agent_forward -from utils.ipfs import get_lido_vote_cid_from_str -from utils.dual_governance import PROPOSAL_STATUS -from utils.test.event_validators.allowed_tokens_registry import validate_add_token_event -from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event -from utils.test.tx_tracing_helpers import ( - group_voting_events_from_receipt, - group_dg_events_from_receipt, - count_vote_items_by_events, - display_voting_events, - display_dg_events -) -from utils.allowed_recipients_registry import ( - unsafe_set_spent_amount, - set_limit_parameters, -) -from utils.test.event_validators.payout import ( - validate_token_payout_event, - Payout, -) -from utils.test.event_validators.allowed_recipients_registry import ( - validate_set_limit_parameter_event, - validate_set_spent_amount_event, -) -from utils.test.event_validators.permission import ( - validate_grant_role_event, - validate_revoke_role_event, - Permission, - validate_permission_grantp_event, - validate_permission_revoke_event, -) -from utils.test.event_validators.node_operators_registry import ( - validate_target_validators_count_changed_event, - TargetValidatorsCountChanged, -) +@pytest.fixture(autouse=True) +def isolation(): + brownie.chain.reset() +def test_vote_v1_v2_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): -class TokenLimit(NamedTuple): - address: str - limit: int - - -# ============================== Import vote ================================= -from scripts.vote_2025_12_10 import start_vote, get_vote_items - - -# ============================== Addresses =================================== -VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" -AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" -EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" -DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" -DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" -ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" -STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" -DEPOSIT_SECURITY_MODULE = "0xffa96d84def2ea035c7ab153d8b991128e3d72fd" -EASY_TRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" -FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" -ACL = "0x9895f0f17cc1d1891b6f18ee0b483b6f221b37bb" -CURATED_MODULE = "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" - -TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" -TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" - -STABLECOINS_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" -LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0xE1f6BaBb445F809B97e3505Ea91749461050F780" -LIDO_LABS_TRUSTED_CALLER = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" -LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY = "0x68267f3D310E9f0FF53a37c141c90B738E1133c2" - -LEGO_LDO_TRUSTED_CALLER = "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030" -LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x00caAeF11EC545B192f16313F53912E453c91458" -LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY = "0x97615f72c3428A393d65A84A3ea6BBD9ad6C0D74" -LEGO_LDO_SPENDABLE_BALANCE = 1_000_000 * 10**18 - -GAS_SUPPLY_STETH_TRUSTED_CALLER = "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53" -GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x200dA0b6a9905A377CF8D469664C65dB267009d1" -GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x49d1363016aA899bba09ae972a1BF200dDf8C55F" -GAS_SUPPLY_STETH_SPENDABLE_BALANCE = 1_000 * 10**18 - -LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" -SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" -DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" -PSM_VARIANT1_ACTIONS = "0xd0A61F2963622e992e6534bde4D52fd0a89F39E0" - - -# ============================== Roles =================================== -CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" -ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" -REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" - - -# ============================== Constants =================================== -CURATED_MODULE_ID = 1 -CURATED_MODULE_TARGET_SHARE_BP = 10000 -CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 10000 -CURATED_MODULE_OLD_MODULE_FEE_BP = 500 -CURATED_MODULE_NEW_MODULE_FEE_BP = 350 -CURATED_MODULE_OLD_TREASURY_FEE_BP = 500 -CURATED_MODULE_NEW_TREASURY_FEE_BP = 650 -CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 -CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 -CURATED_MODULE_NAME = "curated-onchain-v1" - -SDVT_MODULE_ID = 2 -SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 -SDVT_MODULE_MODULE_FEE_BP = 800 -SDVT_MODULE_TREASURY_FEE_BP = 200 -SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 -SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 -SDVT_MODULE_NAME = "SimpleDVT" - -MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 -MATIC_IN_TREASURY_AFTER = 165_781_175_837_137_177 -MATIC_IN_LIDO_LABS_BEFORE = 0 -MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 - -TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 -TRP_ALREADY_SPENT_AFTER = 0 -TRP_LIMIT_AFTER = 15_000_000 * 10**18 -TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC -TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC -TRP_PERIOD_DURATION_MONTHS = 12 - -ALLOWED_TOKENS_BEFORE = 3 -ALLOWED_TOKENS_AFTER = 4 - -A41_NO_ID = 32 -NO_TARGET_LIMIT_SOFT_MODE_BEFORE = 0 -NO_TARGET_LIMIT_SOFT_MODE_AFTER = 1 -NEW_A41_TARGET_LIMIT = 0 -A41_TARGET_CHANGE_REQUEST = TargetValidatorsCountChanged( - nodeOperatorId=A41_NO_ID, - targetValidatorsCount=NEW_A41_TARGET_LIMIT, -) - - -# ============================== Tokens =================================== -MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" -LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" -STETH_TOKEN = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" -WSTETH_TOKEN = "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" -SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" -USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" -USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" -DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" - - -# ============================== Finance Limits =================================== -AMOUNT_LIMITS_LEN_BEFORE = 19 -def amount_limits_before() -> List[Param]: - ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) - eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) - steth_limit = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) - dai_limit = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) - usdc_limit = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) - usdt_limit = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) - - token_arg_index = 0 - amount_arg_index = 2 - - limits = [ - # 0: if (1) then (2) else (3) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) - ), - # 1: (_token == stETH) - Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit.address)), - # 2: { return _amount <= 1_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit.limit)), - # - # 3: else if (4) then (5) else (6) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) - ), - # 4: (_token == DAI) - Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit.address)), - # 5: { return _amount <= 2_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit.limit)), - # - # 6: else if (7) then (8) else (9) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) - ), - # 7: (_token == LDO) - Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit.address)), - # 8: { return _amount <= 5_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit.limit)), - # - # 9: else if (10) then (11) else (12) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, - Op.IF_ELSE, - encode_argument_value_if(condition=10, success=11, failure=12), - ), - # 10: (_token == USDC) - Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit.address)), - # 11: { return _amount <= 2_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit.limit)), - # - # 12: else if (13) then (14) else (15) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, - Op.IF_ELSE, - encode_argument_value_if(condition=13, success=14, failure=15), - ), - # 13: (_token == USDT) - Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit.address)), - # 14: { return _amount <= 2_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit.limit)), - # - # 15: else if (16) then (17) else (18) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, - Op.IF_ELSE, - encode_argument_value_if(condition=16, success=17, failure=18), - ), - # 16: (_token == ETH) - Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit.address)), - # 17: { return _amount <= 1000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit.limit)), - # - # 18: else { return false } - Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), - ] - - assert len(limits) == AMOUNT_LIMITS_LEN_BEFORE - - return limits - -AMOUNT_LIMITS_LEN_AFTER = 22 -ldo_limit_after = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) -eth_limit_after = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) -steth_limit_after = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) -dai_limit_after = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) -usdc_limit_after = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) -usdt_limit_after = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) -susds_limit_after = TokenLimit(SUSDS_TOKEN, 2_000_000 * (10**18)) -def amount_limits_after() -> List[Param]: - - token_arg_index = 0 - amount_arg_index = 2 - - limits = [ - # 0: if (1) then (2) else (3) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) - ), - # 1: (_token == stETH) - Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit_after.address)), - # 2: { return _amount <= 1_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit_after.limit)), - # - # 3: else if (4) then (5) else (6) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) - ), - # 4: (_token == DAI) - Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit_after.address)), - # 5: { return _amount <= 2_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit_after.limit)), - # - # 6: else if (7) then (8) else (9) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) - ), - # 7: (_token == LDO) - Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit_after.address)), - # 8: { return _amount <= 5_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit_after.limit)), - # - # 9: else if (10) then (11) else (12) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, - Op.IF_ELSE, - encode_argument_value_if(condition=10, success=11, failure=12), - ), - # 10: (_token == USDC) - Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit_after.address)), - # 11: { return _amount <= 2_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit_after.limit)), - # - # 12: else if (13) then (14) else (15) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, - Op.IF_ELSE, - encode_argument_value_if(condition=13, success=14, failure=15), - ), - # 13: (_token == USDT) - Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit_after.address)), - # 14: { return _amount <= 2_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit_after.limit)), - # - # 15: else if (16) then (17) else (18) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, - Op.IF_ELSE, - encode_argument_value_if(condition=16, success=17, failure=18), - ), - # 16: (_token == ETH) - Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit_after.address)), - # 17: { return _amount <= 1000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit_after.limit)), - # - # 18: else if (19) then (20) else (21) - Param( - SpecialArgumentID.LOGIC_OP_PARAM_ID, - Op.IF_ELSE, - encode_argument_value_if(condition=19, success=20, failure=21), - ), - # 19: (_token == sUSDS) - Param(token_arg_index, Op.EQ, ArgumentValue(susds_limit_after.address)), - # 20: { return _amount <= 2_000_000 } - Param(amount_arg_index, Op.LTE, ArgumentValue(susds_limit_after.limit)), - # - # 21: else { return false } - Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), - ] - - # Verify that the first part of the after_limits matches the before_limits - for i in range(AMOUNT_LIMITS_LEN_BEFORE - 1): - assert limits[i].id == amount_limits_before()[i].id - assert limits[i].op.value == amount_limits_before()[i].op.value - assert limits[i].value == amount_limits_before()[i].value - - assert len(limits) == AMOUNT_LIMITS_LEN_AFTER - - return limits - - -@pytest.fixture(scope="module") -def dual_governance_proposal_calls(): - - staking_router = interface.StakingRouter(STAKING_ROUTER) - - dg_items = [ - agent_forward([ - ( - staking_router.address, - staking_router.updateStakingModule.encode_input( - CURATED_MODULE_ID, - CURATED_MODULE_TARGET_SHARE_BP, - CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP, - CURATED_MODULE_NEW_MODULE_FEE_BP, - CURATED_MODULE_NEW_TREASURY_FEE_BP, - CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK, - CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, - ), - ), - ]), - agent_forward([ - ( - staking_router.address, - staking_router.updateStakingModule.encode_input( - SDVT_MODULE_ID, - SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, - SDVT_MODULE_MODULE_FEE_BP, - SDVT_MODULE_TREASURY_FEE_BP, - SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, - SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, - ), - ), - ]), - agent_forward([ - ( - staking_router.address, - staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE_AFTER, NEW_A41_TARGET_LIMIT), - ) - ]), - agent_forward([ - unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), - ]), - agent_forward([ - set_limit_parameters( - limit=TRP_LIMIT_AFTER, - period_duration_months=TRP_PERIOD_DURATION_MONTHS, - registry_address=ET_TRP_REGISTRY, - ), - ]), - ] - - # Convert each dg_item to the expected format - proposal_calls = [] - for dg_item in dg_items: - target, data = dg_item # agent_forward returns (target, data) - proposal_calls.append({ - "target": target, - "value": 0, - "data": data - }) - - return proposal_calls - - -def enact_and_test_voting( - helpers, - accounts, - ldo_holder, - vote_ids_from_env, - stranger, - dual_governance_proposal_calls, - EXPECTED_VOTE_ID, - EXPECTED_DG_PROPOSAL_ID, -): - EXPECTED_VOTE_EVENTS_COUNT = 7 - IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" - - # ======================================================================= - # ========================= Arrange variables =========================== - # ======================================================================= - voting = interface.Voting(VOTING) - agent = interface.Agent(AGENT) - timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) - matic_token = interface.ERC20(MATIC_TOKEN) - staking_router = interface.StakingRouter(STAKING_ROUTER) - et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) - acl = interface.ACL(ACL) - stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) - curated_module = interface.NodeOperatorsRegistry(CURATED_MODULE) - - # ========================================================================= - # ======================== Identify or Create vote ======================== - # ========================================================================= - if vote_ids_from_env: - vote_id = vote_ids_from_env[0] - if EXPECTED_VOTE_ID is not None: - assert vote_id == EXPECTED_VOTE_ID - elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: - vote_id = EXPECTED_VOTE_ID - else: - vote_id, _ = start_vote({"from": ldo_holder}, silent=True) - - _, call_script_items = get_vote_items() - onchain_script = voting.getVote(vote_id)["script"] - assert onchain_script == encode_call_script(call_script_items) - - # ========================================================================= - # ============================= Execute Vote ============================== - # ========================================================================= - is_executed = voting.getVote(vote_id)["executed"] - if not is_executed: - # ======================================================================= - # ========================= Before voting checks ======================== - # ======================================================================= - - # Item 1 is DG - skipped here - - # Items 2,4 - assert not stablecoins_allowed_tokens_registry.hasRole( - convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), - VOTING - ) - - # Item 3 - assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() - assert len(allowed_tokens_before) == ALLOWED_TOKENS_BEFORE - assert allowed_tokens_before[0] == DAI_TOKEN - assert allowed_tokens_before[1] == USDT_TOKEN - assert allowed_tokens_before[2] == USDC_TOKEN - - # Items 5,6 - assert acl.getPermissionParamsLength( - ET_EVM_SCRIPT_EXECUTOR, - FINANCE, - convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)) - ) == AMOUNT_LIMITS_LEN_BEFORE - for i in range(AMOUNT_LIMITS_LEN_BEFORE): - id, op, val = acl.getPermissionParam( - ET_EVM_SCRIPT_EXECUTOR, - FINANCE, - convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)), - i - ) - assert id == amount_limits_before()[i].id - assert op == amount_limits_before()[i].op.value - assert val == amount_limits_before()[i].value - - # Item 7 - matic_treasury_balance_before = matic_token.balanceOf(agent.address) - assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE - matic_labs_balance_before = matic_token.balanceOf(LOL_MS) - assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE - - assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH - - vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) - display_voting_events(vote_tx) - vote_events = group_voting_events_from_receipt(vote_tx) - - # ======================================================================= - # ========================= After voting checks ========================= - # ======================================================================= - - # Item 1 is DG - skipped here - - # Items 2,4 - assert not stablecoins_allowed_tokens_registry.hasRole( - convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), - VOTING - ) - - # Item 3 - assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() - assert len(allowed_tokens_before) == ALLOWED_TOKENS_AFTER - assert allowed_tokens_before[0] == DAI_TOKEN - assert allowed_tokens_before[1] == USDT_TOKEN - assert allowed_tokens_before[2] == USDC_TOKEN - assert allowed_tokens_before[3] == SUSDS_TOKEN - - # Items 5,6 - assert acl.getPermissionParamsLength( - ET_EVM_SCRIPT_EXECUTOR, - FINANCE, - convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)) - ) == AMOUNT_LIMITS_LEN_AFTER - for i in range(AMOUNT_LIMITS_LEN_AFTER): - id, op, val = acl.getPermissionParam( - ET_EVM_SCRIPT_EXECUTOR, - FINANCE, - convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)), - i - ) - assert id == amount_limits_after()[i].id - assert op == amount_limits_after()[i].op.value - assert val == amount_limits_after()[i].value - - # check Finance create payment permissions with limits for all allowed tokens - assert acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit], - ) - assert not acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit + 1], - ) - - assert acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(usdt_limit_after.address), convert.to_uint(stranger.address), usdt_limit_after.limit], - ) - assert not acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(usdt_limit_after.address), convert.to_uint(stranger.address), usdt_limit_after.limit + 1], - ) - - assert acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(usdc_limit_after.address), convert.to_uint(stranger.address), usdc_limit_after.limit], - ) - assert not acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(usdc_limit_after.address), convert.to_uint(stranger.address), usdc_limit_after.limit + 1], - ) - - assert acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit], - ) - assert not acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit + 1], - ) - - assert acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit], - ) - assert not acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit + 1], - ) - - assert acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit], - ) - assert not acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit + 1], - ) - - assert acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit], - ) - assert not acl.hasPermission["address,address,bytes32,uint[]"]( - ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit + 1], - ) - - # Item 7 - matic_treasury_balance_after = matic_token.balanceOf(agent.address) - assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER - matic_labs_balance_after = matic_token.balanceOf(LOL_MS) - assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER - # make sure LOL can actually spend the received MATIC - matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) - assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 - assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 - - assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT - assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT - if EXPECTED_DG_PROPOSAL_ID is not None: - assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - - # validate DG Proposal Submit event - validate_dual_governance_submit_event( - vote_events[0], - proposal_id=EXPECTED_DG_PROPOSAL_ID, - proposer=VOTING, - executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, reset Easy Track TRP limit", - proposal_calls=dual_governance_proposal_calls, - ) - - # validate all other voting events - validate_grant_role_event( - events=vote_events[1], - role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), - grant_to=VOTING, - sender=VOTING, - emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, - ) - validate_add_token_event( - event=vote_events[2], - token=SUSDS_TOKEN, - emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY - ) - validate_revoke_role_event( - events=vote_events[3], - role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), - revoke_from=VOTING, - sender=VOTING, - emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, - ) - validate_permission_revoke_event( - event=vote_events[4], - p=Permission( - app=FINANCE, - entity=ET_EVM_SCRIPT_EXECUTOR, - role=web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - ), - emitted_by=ACL, - ) - validate_permission_grantp_event( - event=vote_events[5], - p=Permission( - app=FINANCE, - entity=ET_EVM_SCRIPT_EXECUTOR, - role=web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), - ), - params=amount_limits_after(), - emitted_by=ACL, - ) - validate_token_payout_event( - event=vote_events[6], - p=Payout( - token_addr=MATIC_TOKEN, - from_addr=AGENT, - to_addr=LOL_MS, - amount=MATIC_IN_LIDO_LABS_AFTER), - is_steth=False, - emitted_by=AGENT - ) - - # ======================================================================= - # =========================== Scenario tests ============================ - # ======================================================================= - - # put a lot of tokens into Agent to check Finance/ET limits - prepare_agent_for_dai_payment(30_000_000 * 10**18) - prepare_agent_for_usdt_payment(30_000_000 * 10**6) - prepare_agent_for_usdc_payment(30_000_000 * 10**6) - prepare_agent_for_susds_payment(30_000_000 * 10**18) - prepare_agent_for_ldo_payment(10_000_000 * 10**18) - prepare_agent_for_steth_payment(2_000 * 10**18) - - # check ET limits via Easy Track motion - ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 - et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, LEGO_LDO_SPENDABLE_BALANCE, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, GAS_SUPPLY_STETH_SPENDABLE_BALANCE, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) - - # check Finance limits via Easy Track motion - finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY) - finance_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY) - - # sUSDS can be removed after being added to the allowed list - chain.snapshot() - stablecoins_allowed_tokens_registry.grantRole( - convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), - VOTING, - {"from": VOTING} - ) - assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - stablecoins_allowed_tokens_registry.removeToken( - SUSDS_TOKEN, - {"from": VOTING} - ) - assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) - with reverts("TOKEN_NOT_ALLOWED"): - create_and_enact_payment_motion( - interface.EasyTrack(EASY_TRACK), - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - interface.ERC20(SUSDS_TOKEN), - [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - [1 * 10**18], - stranger, - ) - chain.revert() - - # spending tokens not from the allowed list should fail - chain.snapshot() - with reverts("TOKEN_NOT_ALLOWED"): - create_and_enact_payment_motion( - interface.EasyTrack(EASY_TRACK), - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - interface.ERC20(WSTETH_TOKEN), - [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - [1 * 10**18], - stranger, - ) - chain.revert() - - # spending the allowed token not from the Finance CREATE_PAYMENTS_ROLE's list should fail - chain.snapshot() - stablecoins_allowed_tokens_registry.grantRole( - convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), - VOTING, - {"from": VOTING} - ) - assert not stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) - stablecoins_allowed_tokens_registry.addToken( - WSTETH_TOKEN, - {"from": VOTING} - ) - assert stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) - with reverts("APP_AUTH_FAILED"): - create_and_enact_payment_motion( - interface.EasyTrack(EASY_TRACK), - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - interface.ERC20(WSTETH_TOKEN), - [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], - [1 * 10**18], - stranger, - ) - chain.revert() - - # happy path - usds_wrap_happy_path(stranger) - - -def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): - EXPECTED_DG_EVENTS_COUNT = 5 - - # ======================================================================= - # ========================= Arrange variables =========================== - # ======================================================================= - voting = interface.Voting(VOTING) - agent = interface.Agent(AGENT) - timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) - dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) - staking_router = interface.StakingRouter(STAKING_ROUTER) - et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) - curated_module = interface.NodeOperatorsRegistry(CURATED_MODULE) - - if EXPECTED_DG_PROPOSAL_ID is not None: - details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) - if details["status"] != PROPOSAL_STATUS["executed"]: - # ========================================================================= - # ================== DG before proposal executed checks =================== - # ========================================================================= - - # Item 1.1 - curated_module_before = staking_router.getStakingModule(CURATED_MODULE_ID) - assert curated_module_before['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP - assert curated_module_before['id'] == CURATED_MODULE_ID - assert curated_module_before['priorityExitShareThreshold'] == CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP - assert curated_module_before['stakingModuleFee'] == CURATED_MODULE_OLD_MODULE_FEE_BP - assert curated_module_before['treasuryFee'] == CURATED_MODULE_OLD_TREASURY_FEE_BP - assert curated_module_before['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK - assert curated_module_before['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - assert curated_module_before['name'] == CURATED_MODULE_NAME - - # Item 1.2 - sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) - assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP - assert sdvt_module_before['id'] == SDVT_MODULE_ID - assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP - assert sdvt_module_before['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP - assert sdvt_module_before['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP - assert sdvt_module_before['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK - assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - assert sdvt_module_before['name'] == SDVT_MODULE_NAME - - # Item 1.3 - a41_summary_before = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) - assert a41_summary_before['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_BEFORE - assert a41_summary_before['depositableValidatorsCount'] > 0 - assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" - - # Items 1.4,1.5 - trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() - trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() - assert trp_limit_before == TRP_LIMIT_BEFORE - assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS - assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - trp_already_spent_amount_before - assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP - assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP - - if details["status"] == PROPOSAL_STATUS["submitted"]: - chain.sleep(timelock.getAfterSubmitDelay() + 1) - dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - - if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: - chain.sleep(timelock.getAfterScheduleDelay() + 1) - dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) - display_dg_events(dg_tx) - dg_events = group_dg_events_from_receipt( - dg_tx, - timelock=EMERGENCY_PROTECTED_TIMELOCK, - admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - ) - assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT - assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT - - # validate all DG events - validate_staking_module_update_event( - event=dg_events[0], - module_item=StakingModuleItem( - id=CURATED_MODULE_ID, - name=CURATED_MODULE_NAME, - address=None, - target_share=CURATED_MODULE_TARGET_SHARE_BP, - module_fee=CURATED_MODULE_NEW_MODULE_FEE_BP, - treasury_fee=CURATED_MODULE_NEW_TREASURY_FEE_BP, - priority_exit_share=CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP), - emitted_by=STAKING_ROUTER - ) - validate_staking_module_update_event( - event=dg_events[1], - module_item=StakingModuleItem( - id=SDVT_MODULE_ID, - name=SDVT_MODULE_NAME, - address=None, - target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, - module_fee=SDVT_MODULE_MODULE_FEE_BP, - treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, - priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), - emitted_by=STAKING_ROUTER - ) - validate_target_validators_count_changed_event( - event=dg_events[2], - t=A41_TARGET_CHANGE_REQUEST, - emitted_by=CURATED_MODULE, - ) - validate_set_spent_amount_event( - dg_events[3], - new_spent_amount=0, - emitted_by=ET_TRP_REGISTRY, - ) - validate_set_limit_parameter_event( - dg_events[4], - limit=TRP_LIMIT_AFTER, - period_duration_month=TRP_PERIOD_DURATION_MONTHS, - period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, - emitted_by=ET_TRP_REGISTRY, - ) - - # ========================================================================= - # ==================== After DG proposal executed checks ================== - # ========================================================================= - - # Item 1.1 - curated_module_after = staking_router.getStakingModule(CURATED_MODULE_ID) - assert curated_module_after['stakingModuleFee'] == CURATED_MODULE_NEW_MODULE_FEE_BP - assert curated_module_after['treasuryFee'] == CURATED_MODULE_NEW_TREASURY_FEE_BP - assert curated_module_after['id'] == CURATED_MODULE_ID - assert curated_module_after['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP - assert curated_module_after['priorityExitShareThreshold'] == CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP - assert curated_module_after['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK - assert curated_module_after['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - assert curated_module_after['name'] == CURATED_MODULE_NAME - # additional checks to make sure no other fields were changed (if before state is available) - if curated_module_before is not None: - assert curated_module_after['id'] == curated_module_before['id'] - assert curated_module_after['stakingModuleAddress'] == curated_module_before['stakingModuleAddress'] - assert curated_module_after['stakeShareLimit'] == curated_module_before['stakeShareLimit'] - assert curated_module_after['status'] == curated_module_before['status'] - assert curated_module_after['name'] == curated_module_before['name'] - assert curated_module_after['lastDepositAt'] == curated_module_before['lastDepositAt'] - assert curated_module_after['lastDepositBlock'] == curated_module_before['lastDepositBlock'] - assert curated_module_after['exitedValidatorsCount'] == curated_module_before['exitedValidatorsCount'] - assert curated_module_after['maxDepositsPerBlock'] == curated_module_before['maxDepositsPerBlock'] - assert curated_module_after['minDepositBlockDistance'] == curated_module_before['minDepositBlockDistance'] - assert curated_module_after['priorityExitShareThreshold'] == curated_module_before['priorityExitShareThreshold'] - assert len(curated_module_after.items()) == len(curated_module_before.items()) - assert len(curated_module_after.items()) == 13 - - # Item 1.2 - sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) - assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP - assert sdvt_module_after['id'] == SDVT_MODULE_ID - assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP - assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP - assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP - assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK - assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE - assert sdvt_module_after['name'] == SDVT_MODULE_NAME - # additional checks to make sure no other fields were changed (if before state is available) - if sdvt_module_before is not None: - assert sdvt_module_after['id'] == sdvt_module_before['id'] - assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] - assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] - assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] - assert sdvt_module_after['status'] == sdvt_module_before['status'] - assert sdvt_module_after['name'] == sdvt_module_before['name'] - assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] - assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] - assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] - assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] - assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] - assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] - assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) - assert len(sdvt_module_after.items()) == 13 - - # Item 1.3 - a41_summary_after = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) - assert a41_summary_after['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_AFTER - assert a41_summary_after['depositableValidatorsCount'] == 0 - assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" - # additional checks to make sure no other fields were changed (if before state is available) - if a41_summary_before is not None: - assert a41_summary_after['targetValidatorsCount'] == a41_summary_before['targetValidatorsCount'] - assert a41_summary_after['stuckValidatorsCount'] == a41_summary_before['stuckValidatorsCount'] - assert a41_summary_after['refundedValidatorsCount'] == a41_summary_before['refundedValidatorsCount'] - assert a41_summary_after['stuckPenaltyEndTimestamp'] == a41_summary_before['stuckPenaltyEndTimestamp'] - assert a41_summary_after['totalExitedValidators'] == a41_summary_before['totalExitedValidators'] - assert a41_summary_after['totalDepositedValidators'] == a41_summary_before['totalDepositedValidators'] - assert len(a41_summary_after.items()) == 8 - - # Items 1.4,1.5 - trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() - trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() - assert trp_limit_after == TRP_LIMIT_AFTER - assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS - assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER - assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER - assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP - assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP - - # scenraio test for TRP ET factory behavior after the vote - trp_limit_test(stranger) - -def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): - EXPECTED_VOTE_ID = 194 - EXPECTED_DG_PROPOSAL_ID = 6 - - enact_and_test_voting( - helpers, - accounts, - ldo_holder, - vote_ids_from_env, - stranger, - dual_governance_proposal_calls, - EXPECTED_VOTE_ID, - EXPECTED_DG_PROPOSAL_ID, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + 194, 6, ) - enact_and_test_dg( - stranger, - EXPECTED_DG_PROPOSAL_ID, + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, ) + lidov3.enact_and_test_dg(stranger, 6) -def trp_limit_test(stranger): - - easy_track = interface.EasyTrack(EASY_TRACK) - ldo_token = interface.ERC20(LDO_TOKEN) - to_spend = TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER - max_spend_at_once = 5_000_000 * 10**18 - trp_committee_account = accounts.at(TRP_COMMITTEE, force=True) - - chain.snapshot() + ops.enact_and_test_dg(stranger, 7) - # check that there is no way to spend more then expected - with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): - create_and_enact_payment_motion( - easy_track, - TRP_COMMITTEE, - TRP_TOP_UP_EVM_SCRIPT_FACTORY, - ldo_token, - [trp_committee_account], - [to_spend + 1], - stranger, - ) +def test_vote_v1_v2_dg2_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - # spend all in several transfers - recipients = [] - amounts = [] - while to_spend > 0: - recipients.append(trp_committee_account) - amounts.append(min(max_spend_at_once, to_spend)) - to_spend -= min(max_spend_at_once, to_spend) - - create_and_enact_payment_motion( - easy_track, - TRP_COMMITTEE, - TRP_TOP_UP_EVM_SCRIPT_FACTORY, - ldo_token, - recipients, - amounts, - stranger, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + 194, 6, ) - # make sure there is nothing left so that you can't spend anymore - with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): - create_and_enact_payment_motion( - easy_track, - TRP_COMMITTEE, - TRP_TOP_UP_EVM_SCRIPT_FACTORY, - ldo_token, - [trp_committee_account], - [1], - stranger, - ) - - chain.revert() - -def et_limit_test(stranger, token, max_spend_at_once, to_spend, TRUSTED_CALLER, TOP_UP_ALLOWED_RECIPIENTS_FACTORY): - - easy_track = interface.EasyTrack(EASY_TRACK) - trusted_caller_account = accounts.at(TRUSTED_CALLER, force=True) - - chain.snapshot() - - # check that there is no way to spend more then expected - with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): - create_and_enact_payment_motion( - easy_track, - TRUSTED_CALLER, - TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - token, - [trusted_caller_account], - [to_spend + 1], - stranger, - ) - - # spend all in several transfers - recipients = [] - amounts = [] - while to_spend > 0: - recipients.append(trusted_caller_account) - amounts.append(min(max_spend_at_once, to_spend)) - to_spend -= min(max_spend_at_once, to_spend) - - create_and_enact_payment_motion( - easy_track, - TRUSTED_CALLER, - TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - token, - recipients, - amounts, - stranger, + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, ) - # make sure there is nothing left so that you can't spend anymore - with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): - create_and_enact_payment_motion( - easy_track, - TRUSTED_CALLER, - TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - token, - [trusted_caller_account], - [1], - stranger, - ) + ops.enact_and_test_dg(stranger, 7) - chain.revert() + lidov3.enact_and_test_dg(stranger, 6) +def test_vote_v1_dg1_v2_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): -def finance_limit_test(stranger, token, to_spend, decimals, TRUSTED_CALLER, TOP_UP_ALLOWED_RECIPIENTS_FACTORY, ALLOWED_RECIPIENTS_REGISTRY): - - easy_track = interface.EasyTrack(EASY_TRACK) - trusted_caller_account = accounts.at(TRUSTED_CALLER, force=True) - - chain.snapshot() - - # for Finance limit check - we first raise ET limits to 10 x finance_limit to be able to spend via Finance - interface.AllowedRecipientRegistry(ALLOWED_RECIPIENTS_REGISTRY).setLimitParameters( - (to_spend / (10**decimals) * 10**18) * 10, # 10 x finance_limit - 3, # 3 months - {"from": AGENT} + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + 194, 6, ) - # check that there is no way to spend more then expected - with reverts("APP_AUTH_FAILED"): - create_and_enact_payment_motion( - easy_track, - TRUSTED_CALLER, - TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - token, - [trusted_caller_account], - [to_spend + 1], - stranger, - ) + lidov3.enact_and_test_dg(stranger, 6) - # spend the allowed balance - create_and_enact_payment_motion( - easy_track, - TRUSTED_CALLER, - TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - token, - [trusted_caller_account], - [to_spend], - stranger, + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, ) - chain.revert() - - -def usds_wrap_happy_path(stranger): - USDC_FOR_TRANSFER = 1000 - USDS_TOKEN = "0xdC035D45d973E3EC169d2276DDab16f1e407384F" - - easy_track = interface.EasyTrack(EASY_TRACK) - usdc = interface.Usdc(USDC_TOKEN) - psmVariant1Actions = interface.PSMVariant1Actions(PSM_VARIANT1_ACTIONS) - usds_token = interface.Usds(USDS_TOKEN) - susds_token = interface.Susds(SUSDS_TOKEN) - - eoa = accounts[0] - - chain.snapshot() + ops.enact_and_test_dg(stranger, 7) - initial_susds_agent_balance = susds_token.balanceOf(AGENT) +def test_vote_v2_v1_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - # fund EOA with USDC from Treasury - interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).addRecipient( - eoa.address, - "EOA_test", - {"from": AGENT} + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, ) - create_and_enact_payment_motion( - easy_track, - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - usdc, - [eoa], - [USDC_FOR_TRANSFER * 10**6], - stranger, - ) - assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 - assert usds_token.balanceOf(eoa.address) == 0 - assert susds_token.balanceOf(eoa.address) == 0 - - # wrap USDC to sUSDS via PSM - usdc.approve(PSM_VARIANT1_ACTIONS, USDC_FOR_TRANSFER * 10**6, {"from": eoa}) - psmVariant1Actions.swapAndDeposit(eoa.address, USDC_FOR_TRANSFER * 10**6, USDC_FOR_TRANSFER * 10**18, {"from": eoa}) - assert usdc.balanceOf(eoa.address) == 0 - assert usds_token.balanceOf(eoa.address) == 0 - susds_balance = susds_token.balanceOf(eoa.address) - assert susds_balance <= USDC_FOR_TRANSFER * 10**18 - assert susds_balance >= USDC_FOR_TRANSFER * 10**18 * 0.9 - # send sUSDS back to Treasury - susds_token.transfer(AGENT, susds_balance, {"from": eoa}) - assert susds_token.balanceOf(eoa.address) == 0 - assert susds_token.balanceOf(AGENT) == susds_balance + initial_susds_agent_balance - print("swapped", USDC_FOR_TRANSFER, "USDC to", susds_balance / 10**18, "sUSDS") - - # send sUSDS again to EOA via Easy Track payment from Treasury - create_and_enact_payment_motion( - easy_track, - LIDO_LABS_TRUSTED_CALLER, - LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - susds_token, - [eoa], - [susds_balance], - stranger, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + 195, 7, ) - assert susds_token.balanceOf(eoa.address) == susds_balance - assert susds_token.balanceOf(AGENT) == initial_susds_agent_balance - - # wait 1 year to accumulate interest on sUSDS - chain.sleep(365 * 24 * 3600) - chain.mine() - susds_token.drip({"from": eoa}) - INTEREST_RATE = 0.04 - - # unwrap sUSDS to USDC - susds_token.approve(PSM_VARIANT1_ACTIONS, susds_balance, {"from": eoa}) - psmVariant1Actions.withdrawAndSwap(eoa.address, USDC_FOR_TRANSFER * 10**6 * (1 + INTEREST_RATE), USDC_FOR_TRANSFER * 10**18 * (1 + INTEREST_RATE), {"from": eoa}) - usdc_balance = usdc.balanceOf(eoa.address) - print("swapped", susds_balance / 10**18, "sUSDS to", usdc_balance / 10**6, "USDC, leftover:", susds_token.balanceOf(eoa.address) / 10**18, "sUSDS") - assert susds_token.balanceOf(eoa.address) < 5.0 * 10**18 # leftover from interest surplus - assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 * (1 + INTEREST_RATE) - - chain.revert() - - -def prepare_agent_for_dai_payment(amount: int): - agent, dai = interface.Agent(AGENT), interface.Dai(DAI_TOKEN) - if dai.balanceOf(agent) < amount: - dai_ward_impersonated = accounts.at("0x9759A6Ac90977b93B58547b4A71c78317f391A28", force=True) - dai.mint(agent, amount, {"from": dai_ward_impersonated}) - assert dai.balanceOf(agent) >= amount, f"Insufficient DAI balance" + lidov3.enact_and_test_dg(stranger, 7) + ops.enact_and_test_dg(stranger, 6) -def prepare_agent_for_usdc_payment(amount: int): - agent, usdc = interface.Agent(AGENT), interface.Usdc(USDC_TOKEN) - if usdc.balanceOf(agent) < amount: - usdc_minter = accounts.at("0x5B6122C109B78C6755486966148C1D70a50A47D7", force=True) - usdc_controller = accounts.at("0x79E0946e1C186E745f1352d7C21AB04700C99F71", force=True) - usdc_master_minter = interface.UsdcMasterMinter("0xE982615d461DD5cD06575BbeA87624fda4e3de17") - usdc_master_minter.incrementMinterAllowance(amount, {"from": usdc_controller}) - usdc.mint(agent, amount, {"from": usdc_minter}) +def test_vote_v2_v1_dg2_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - assert usdc.balanceOf(agent) >= amount, "Insufficient USDC balance" - - -def prepare_agent_for_usdt_payment(amount: int): - agent, usdt = interface.Agent(AGENT), interface.Usdt(USDT_TOKEN) - if usdt.balanceOf(agent) < amount: - usdt_owner = accounts.at("0xC6CDE7C39eB2f0F0095F41570af89eFC2C1Ea828", force=True) - usdt.issue(amount, {"from": usdt_owner}) - usdt.transfer(agent, amount, {"from": usdt_owner}) - - assert usdt.balanceOf(agent) >= amount, "Insufficient USDT balance" + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, + ) + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + 195, 7, + ) -def prepare_agent_for_susds_payment(amount: int): - agent, susds = interface.Agent(AGENT), interface.ERC20(SUSDS_TOKEN) - if susds.balanceOf(agent) < amount: - susds_whale = accounts.at("0xBc65ad17c5C0a2A4D159fa5a503f4992c7B545FE", force=True) - susds.transfer(agent, amount, {"from": susds_whale}) + ops.enact_and_test_dg(stranger, 6) - assert susds.balanceOf(agent) >= amount, "Insufficient sUSDS balance" + lidov3.enact_and_test_dg(stranger, 7) +def test_vote_v2_dg2_v1_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): -def prepare_agent_for_ldo_payment(amount: int): - agent, ldo = interface.Agent(AGENT), interface.ERC20(LDO_TOKEN) - assert ldo.balanceOf(agent) >= amount, "Insufficient LDO balance 🫡" + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, + ) + ops.enact_and_test_dg(stranger, 6) -def prepare_agent_for_steth_payment(amount: int): - STETH_TRANSFER_MAX_DELTA = 2 + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + 195, 7, + ) - agent, steth = interface.Agent(AGENT), interface.Lido(STETH_TOKEN) - eth_whale = accounts.at("0x00000000219ab540356cBB839Cbe05303d7705Fa", force=True) - if steth.balanceOf(agent) < amount: - steth.submit(ZERO_ADDRESS, {"from": eth_whale, "value": amount + 2 * STETH_TRANSFER_MAX_DELTA}) - steth.transfer(agent, amount + STETH_TRANSFER_MAX_DELTA, {"from": eth_whale}) - assert steth.balanceOf(agent) >= amount, "Insufficient stETH balance" + lidov3.enact_and_test_dg(stranger, 7) \ No newline at end of file diff --git a/tests/test_2025_12_10_mainnet_v3.py b/tests/utils_test_2025_12_10_lidov3.py similarity index 98% rename from tests/test_2025_12_10_mainnet_v3.py rename to tests/utils_test_2025_12_10_lidov3.py index fec0446b2..a4ea7dbf4 100644 --- a/tests/test_2025_12_10_mainnet_v3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -1,7 +1,5 @@ -from typing import Optional from brownie import chain, interface, web3, convert from brownie.network.transaction import TransactionReceipt -import pytest from utils.test.tx_tracing_helpers import ( group_voting_events_from_receipt, @@ -20,7 +18,6 @@ from utils.permissions import encode_oz_grant_role, encode_oz_revoke_role from utils.test.event_validators.easy_track import validate_evmscript_factory_added_event, EVMScriptFactoryAdded from utils.easy_track import create_permissions -from brownie.network.event import EventDict from utils.test.event_validators.common import validate_events_chain from utils.test.event_validators.proxy import validate_proxy_upgrade_event from utils.test.event_validators.permission import validate_grant_role_event, validate_revoke_role_event @@ -271,7 +268,6 @@ def validate_upgrade_finished_event(events) -> None: # ============================== Test functions ============================== # ============================================================================ -@pytest.fixture(scope="module") def dual_governance_proposal_calls(): """Returns list of dual governance proposal calls for events checking""" @@ -451,7 +447,6 @@ def enact_and_test_voting( accounts, ldo_holder, vote_ids_from_env, - dual_governance_proposal_calls, expected_vote_id, expected_dg_proposal_id, ): @@ -552,7 +547,7 @@ def enact_and_test_voting( proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, metadata="TODO DG proposal description", - proposal_calls=dual_governance_proposal_calls, + proposal_calls=dual_governance_proposal_calls(), ) # Validate EasyTrack bypass events for new factories @@ -907,22 +902,3 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # Step 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent after upgrade" - -def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): - EXPECTED_VOTE_ID = 194 - EXPECTED_DG_PROPOSAL_ID = 6 - - enact_and_test_voting( - helpers, - accounts, - ldo_holder, - vote_ids_from_env, - dual_governance_proposal_calls, - expected_vote_id=EXPECTED_VOTE_ID, - expected_dg_proposal_id=EXPECTED_DG_PROPOSAL_ID, - ) - - enact_and_test_dg( - stranger, - expected_dg_proposal_id=EXPECTED_DG_PROPOSAL_ID, - ) diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py new file mode 100644 index 000000000..5a215a128 --- /dev/null +++ b/tests/utils_test_2025_12_10_operations.py @@ -0,0 +1,1256 @@ +from typing import List, NamedTuple + +from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 +from brownie.network.transaction import TransactionReceipt +from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op +from utils.test.easy_track_helpers import create_and_enact_payment_motion +from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem +from utils.evm_script import encode_call_script +from utils.voting import find_metadata_by_vote_id +from utils.agent import agent_forward +from utils.ipfs import get_lido_vote_cid_from_str +from utils.dual_governance import PROPOSAL_STATUS +from utils.test.event_validators.allowed_tokens_registry import validate_add_token_event +from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event +from utils.test.tx_tracing_helpers import ( + group_voting_events_from_receipt, + group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events +) +from utils.allowed_recipients_registry import ( + unsafe_set_spent_amount, + set_limit_parameters, +) +from utils.test.event_validators.payout import ( + validate_token_payout_event, + Payout, +) +from utils.test.event_validators.allowed_recipients_registry import ( + validate_set_limit_parameter_event, + validate_set_spent_amount_event, +) +from utils.test.event_validators.permission import ( + validate_grant_role_event, + validate_revoke_role_event, + Permission, + validate_permission_grantp_event, + validate_permission_revoke_event, +) +from utils.test.event_validators.node_operators_registry import ( + validate_target_validators_count_changed_event, + TargetValidatorsCountChanged, +) + + +class TokenLimit(NamedTuple): + address: str + limit: int + + +# ============================== Import vote ================================= +from scripts.vote_2025_12_10 import start_vote, get_vote_items + + +# ============================== Addresses =================================== +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" +EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" +DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" +ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" +DEPOSIT_SECURITY_MODULE = "0xffa96d84def2ea035c7ab153d8b991128e3d72fd" +EASY_TRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" +FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" +ACL = "0x9895f0f17cc1d1891b6f18ee0b483b6f221b37bb" +CURATED_MODULE = "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" + +TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" +TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" + +STABLECOINS_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" +LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0xE1f6BaBb445F809B97e3505Ea91749461050F780" +LIDO_LABS_TRUSTED_CALLER = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" +LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY = "0x68267f3D310E9f0FF53a37c141c90B738E1133c2" + +LEGO_LDO_TRUSTED_CALLER = "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030" +LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x00caAeF11EC545B192f16313F53912E453c91458" +LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY = "0x97615f72c3428A393d65A84A3ea6BBD9ad6C0D74" +LEGO_LDO_SPENDABLE_BALANCE = 1_000_000 * 10**18 + +GAS_SUPPLY_STETH_TRUSTED_CALLER = "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53" +GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x200dA0b6a9905A377CF8D469664C65dB267009d1" +GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x49d1363016aA899bba09ae972a1BF200dDf8C55F" +GAS_SUPPLY_STETH_SPENDABLE_BALANCE = 1_000 * 10**18 + +LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" +SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" +DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" +PSM_VARIANT1_ACTIONS = "0xd0A61F2963622e992e6534bde4D52fd0a89F39E0" + + +# ============================== Roles =================================== +CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" +ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" +REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" + + +# ============================== Constants =================================== +CURATED_MODULE_ID = 1 +CURATED_MODULE_TARGET_SHARE_BP = 10000 +CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 10000 +CURATED_MODULE_OLD_MODULE_FEE_BP = 500 +CURATED_MODULE_NEW_MODULE_FEE_BP = 350 +CURATED_MODULE_OLD_TREASURY_FEE_BP = 500 +CURATED_MODULE_NEW_TREASURY_FEE_BP = 650 +CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +CURATED_MODULE_NAME = "curated-onchain-v1" + +SDVT_MODULE_ID = 2 +SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 +SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_MODULE_FEE_BP = 800 +SDVT_MODULE_TREASURY_FEE_BP = 200 +SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +SDVT_MODULE_NAME = "SimpleDVT" + +MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 +MATIC_IN_TREASURY_AFTER = 165_781_175_837_137_177 +MATIC_IN_LIDO_LABS_BEFORE = 0 +MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 + +TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 +TRP_ALREADY_SPENT_AFTER = 0 +TRP_LIMIT_AFTER = 15_000_000 * 10**18 +TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC +TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC +TRP_PERIOD_DURATION_MONTHS = 12 + +ALLOWED_TOKENS_BEFORE = 3 +ALLOWED_TOKENS_AFTER = 4 + +A41_NO_ID = 32 +NO_TARGET_LIMIT_SOFT_MODE_BEFORE = 0 +NO_TARGET_LIMIT_SOFT_MODE_AFTER = 1 +NEW_A41_TARGET_LIMIT = 0 +A41_TARGET_CHANGE_REQUEST = TargetValidatorsCountChanged( + nodeOperatorId=A41_NO_ID, + targetValidatorsCount=NEW_A41_TARGET_LIMIT, +) + + +# ============================== Tokens =================================== +MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" +LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" +STETH_TOKEN = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" +WSTETH_TOKEN = "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" +SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" +USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" +DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" + + +# ============================== Finance Limits =================================== +AMOUNT_LIMITS_LEN_BEFORE = 19 +def amount_limits_before() -> List[Param]: + ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) + eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) + steth_limit = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) + dai_limit = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) + usdc_limit = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) + usdt_limit = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) + + token_arg_index = 0 + amount_arg_index = 2 + + limits = [ + # 0: if (1) then (2) else (3) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) + ), + # 1: (_token == stETH) + Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit.address)), + # 2: { return _amount <= 1_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit.limit)), + # + # 3: else if (4) then (5) else (6) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) + ), + # 4: (_token == DAI) + Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit.address)), + # 5: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit.limit)), + # + # 6: else if (7) then (8) else (9) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) + ), + # 7: (_token == LDO) + Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit.address)), + # 8: { return _amount <= 5_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit.limit)), + # + # 9: else if (10) then (11) else (12) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=10, success=11, failure=12), + ), + # 10: (_token == USDC) + Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit.address)), + # 11: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit.limit)), + # + # 12: else if (13) then (14) else (15) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=13, success=14, failure=15), + ), + # 13: (_token == USDT) + Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit.address)), + # 14: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit.limit)), + # + # 15: else if (16) then (17) else (18) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=16, success=17, failure=18), + ), + # 16: (_token == ETH) + Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit.address)), + # 17: { return _amount <= 1000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit.limit)), + # + # 18: else { return false } + Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), + ] + + assert len(limits) == AMOUNT_LIMITS_LEN_BEFORE + + return limits + +AMOUNT_LIMITS_LEN_AFTER = 22 +ldo_limit_after = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) +eth_limit_after = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) +steth_limit_after = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) +dai_limit_after = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) +usdc_limit_after = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) +usdt_limit_after = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) +susds_limit_after = TokenLimit(SUSDS_TOKEN, 2_000_000 * (10**18)) +def amount_limits_after() -> List[Param]: + + token_arg_index = 0 + amount_arg_index = 2 + + limits = [ + # 0: if (1) then (2) else (3) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) + ), + # 1: (_token == stETH) + Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit_after.address)), + # 2: { return _amount <= 1_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit_after.limit)), + # + # 3: else if (4) then (5) else (6) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) + ), + # 4: (_token == DAI) + Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit_after.address)), + # 5: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit_after.limit)), + # + # 6: else if (7) then (8) else (9) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) + ), + # 7: (_token == LDO) + Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit_after.address)), + # 8: { return _amount <= 5_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit_after.limit)), + # + # 9: else if (10) then (11) else (12) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=10, success=11, failure=12), + ), + # 10: (_token == USDC) + Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit_after.address)), + # 11: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit_after.limit)), + # + # 12: else if (13) then (14) else (15) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=13, success=14, failure=15), + ), + # 13: (_token == USDT) + Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit_after.address)), + # 14: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit_after.limit)), + # + # 15: else if (16) then (17) else (18) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=16, success=17, failure=18), + ), + # 16: (_token == ETH) + Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit_after.address)), + # 17: { return _amount <= 1000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit_after.limit)), + # + # 18: else if (19) then (20) else (21) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=19, success=20, failure=21), + ), + # 19: (_token == sUSDS) + Param(token_arg_index, Op.EQ, ArgumentValue(susds_limit_after.address)), + # 20: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(susds_limit_after.limit)), + # + # 21: else { return false } + Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), + ] + + # Verify that the first part of the after_limits matches the before_limits + for i in range(AMOUNT_LIMITS_LEN_BEFORE - 1): + assert limits[i].id == amount_limits_before()[i].id + assert limits[i].op.value == amount_limits_before()[i].op.value + assert limits[i].value == amount_limits_before()[i].value + + assert len(limits) == AMOUNT_LIMITS_LEN_AFTER + + return limits + + +def dual_governance_proposal_calls(): + + staking_router = interface.StakingRouter(STAKING_ROUTER) + + dg_items = [ + agent_forward([ + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + CURATED_MODULE_ID, + CURATED_MODULE_TARGET_SHARE_BP, + CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + CURATED_MODULE_NEW_MODULE_FEE_BP, + CURATED_MODULE_NEW_TREASURY_FEE_BP, + CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK, + CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), + agent_forward([ + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + SDVT_MODULE_ID, + SDVT_MODULE_NEW_TARGET_SHARE_BP, + SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_MODULE_FEE_BP, + SDVT_MODULE_TREASURY_FEE_BP, + SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, + SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), + agent_forward([ + ( + staking_router.address, + staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE_AFTER, NEW_A41_TARGET_LIMIT), + ) + ]), + agent_forward([ + unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), + ]), + agent_forward([ + set_limit_parameters( + limit=TRP_LIMIT_AFTER, + period_duration_months=TRP_PERIOD_DURATION_MONTHS, + registry_address=ET_TRP_REGISTRY, + ), + ]), + ] + + # Convert each dg_item to the expected format + proposal_calls = [] + for dg_item in dg_items: + target, data = dg_item # agent_forward returns (target, data) + proposal_calls.append({ + "target": target, + "value": 0, + "data": data + }) + + return proposal_calls + + +def enact_and_test_voting( + helpers, + accounts, + ldo_holder, + vote_ids_from_env, + stranger, + EXPECTED_VOTE_ID, + EXPECTED_DG_PROPOSAL_ID, +): + EXPECTED_VOTE_EVENTS_COUNT = 7 + IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + voting = interface.Voting(VOTING) + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + matic_token = interface.ERC20(MATIC_TOKEN) + acl = interface.ACL(ACL) + stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) + + # ========================================================================= + # ======================== Identify or Create vote ======================== + # ========================================================================= + if vote_ids_from_env: + vote_id = vote_ids_from_env[0] + if EXPECTED_VOTE_ID is not None: + assert vote_id == EXPECTED_VOTE_ID + elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: + vote_id = EXPECTED_VOTE_ID + else: + vote_id, _ = start_vote({"from": ldo_holder}, silent=True) + + _, call_script_items = get_vote_items() + onchain_script = voting.getVote(vote_id)["script"] + assert onchain_script == encode_call_script(call_script_items) + + # ========================================================================= + # ============================= Execute Vote ============================== + # ========================================================================= + is_executed = voting.getVote(vote_id)["executed"] + if not is_executed: + # ======================================================================= + # ========================= Before voting checks ======================== + # ======================================================================= + + # Item 1 is DG - skipped here + + # Items 2,4 + assert not stablecoins_allowed_tokens_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING + ) + + # Item 3 + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() + assert len(allowed_tokens_before) == ALLOWED_TOKENS_BEFORE + assert allowed_tokens_before[0] == DAI_TOKEN + assert allowed_tokens_before[1] == USDT_TOKEN + assert allowed_tokens_before[2] == USDC_TOKEN + + # Items 5,6 + assert acl.getPermissionParamsLength( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)) + ) == AMOUNT_LIMITS_LEN_BEFORE + for i in range(AMOUNT_LIMITS_LEN_BEFORE): + id, op, val = acl.getPermissionParam( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)), + i + ) + assert id == amount_limits_before()[i].id + assert op == amount_limits_before()[i].op.value + assert val == amount_limits_before()[i].value + + # Item 7 + matic_treasury_balance_before = matic_token.balanceOf(agent.address) + assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE + matic_labs_balance_before = matic_token.balanceOf(LOL_MS) + assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE + + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH + + vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) + display_voting_events(vote_tx) + vote_events = group_voting_events_from_receipt(vote_tx) + + # ======================================================================= + # ========================= After voting checks ========================= + # ======================================================================= + + # Item 1 is DG - skipped here + + # Items 2,4 + assert not stablecoins_allowed_tokens_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING + ) + + # Item 3 + assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() + assert len(allowed_tokens_before) == ALLOWED_TOKENS_AFTER + assert allowed_tokens_before[0] == DAI_TOKEN + assert allowed_tokens_before[1] == USDT_TOKEN + assert allowed_tokens_before[2] == USDC_TOKEN + assert allowed_tokens_before[3] == SUSDS_TOKEN + + # Items 5,6 + assert acl.getPermissionParamsLength( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)) + ) == AMOUNT_LIMITS_LEN_AFTER + for i in range(AMOUNT_LIMITS_LEN_AFTER): + id, op, val = acl.getPermissionParam( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)), + i + ) + assert id == amount_limits_after()[i].id + assert op == amount_limits_after()[i].op.value + assert val == amount_limits_after()[i].value + + # check Finance create payment permissions with limits for all allowed tokens + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdt_limit_after.address), convert.to_uint(stranger.address), usdt_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdt_limit_after.address), convert.to_uint(stranger.address), usdt_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdc_limit_after.address), convert.to_uint(stranger.address), usdc_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdc_limit_after.address), convert.to_uint(stranger.address), usdc_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit + 1], + ) + + # Item 7 + matic_treasury_balance_after = matic_token.balanceOf(agent.address) + assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER + matic_labs_balance_after = matic_token.balanceOf(LOL_MS) + assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER + # make sure LOL can actually spend the received MATIC + matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) + assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 + assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 + + assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT + assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT + if EXPECTED_DG_PROPOSAL_ID is not None: + assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() + + # validate DG Proposal Submit event + validate_dual_governance_submit_event( + vote_events[0], + proposal_id=EXPECTED_DG_PROPOSAL_ID, + proposer=VOTING, + executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + metadata="Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, reset Easy Track TRP limit", + proposal_calls=dual_governance_proposal_calls(), + ) + + # validate all other voting events + validate_grant_role_event( + events=vote_events[1], + role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), + grant_to=VOTING, + sender=VOTING, + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, + ) + validate_add_token_event( + event=vote_events[2], + token=SUSDS_TOKEN, + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY + ) + validate_revoke_role_event( + events=vote_events[3], + role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), + revoke_from=VOTING, + sender=VOTING, + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, + ) + validate_permission_revoke_event( + event=vote_events[4], + p=Permission( + app=FINANCE, + entity=ET_EVM_SCRIPT_EXECUTOR, + role=web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + ), + emitted_by=ACL, + ) + validate_permission_grantp_event( + event=vote_events[5], + p=Permission( + app=FINANCE, + entity=ET_EVM_SCRIPT_EXECUTOR, + role=web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + ), + params=amount_limits_after(), + emitted_by=ACL, + ) + validate_token_payout_event( + event=vote_events[6], + p=Payout( + token_addr=MATIC_TOKEN, + from_addr=AGENT, + to_addr=LOL_MS, + amount=MATIC_IN_LIDO_LABS_AFTER), + is_steth=False, + emitted_by=AGENT + ) + + # ======================================================================= + # =========================== Scenario tests ============================ + # ======================================================================= + + # put a lot of tokens into Agent to check Finance/ET limits + prepare_agent_for_dai_payment(30_000_000 * 10**18) + prepare_agent_for_usdt_payment(30_000_000 * 10**6) + prepare_agent_for_usdc_payment(30_000_000 * 10**6) + prepare_agent_for_susds_payment(30_000_000 * 10**18) + prepare_agent_for_ldo_payment(10_000_000 * 10**18) + prepare_agent_for_steth_payment(2_000 * 10**18) + + # check ET limits via Easy Track motion + ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 + et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, LEGO_LDO_SPENDABLE_BALANCE, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, GAS_SUPPLY_STETH_SPENDABLE_BALANCE, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + + # check Finance limits via Easy Track motion + finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY) + + # sUSDS can be removed after being added to the allowed list + chain.snapshot() + stablecoins_allowed_tokens_registry.grantRole( + convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), + VOTING, + {"from": VOTING} + ) + assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + stablecoins_allowed_tokens_registry.removeToken( + SUSDS_TOKEN, + {"from": VOTING} + ) + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + with reverts("TOKEN_NOT_ALLOWED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(SUSDS_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # spending tokens not from the allowed list should fail + chain.snapshot() + with reverts("TOKEN_NOT_ALLOWED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(WSTETH_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # spending the allowed token not from the Finance CREATE_PAYMENTS_ROLE's list should fail + chain.snapshot() + stablecoins_allowed_tokens_registry.grantRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING, + {"from": VOTING} + ) + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + stablecoins_allowed_tokens_registry.addToken( + WSTETH_TOKEN, + {"from": VOTING} + ) + assert stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + with reverts("APP_AUTH_FAILED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(WSTETH_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # happy path + usds_wrap_happy_path(stranger) + + +def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): + EXPECTED_DG_EVENTS_COUNT = 5 + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + staking_router = interface.StakingRouter(STAKING_ROUTER) + et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) + curated_module = interface.NodeOperatorsRegistry(CURATED_MODULE) + + curated_module_before = None + sdvt_module_before = None + a41_summary_before = None + + if EXPECTED_DG_PROPOSAL_ID is not None: + details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) + if details["status"] != PROPOSAL_STATUS["executed"]: + # ========================================================================= + # ================== DG before proposal executed checks =================== + # ========================================================================= + + # Item 1.1 + curated_module_before = staking_router.getStakingModule(CURATED_MODULE_ID) + assert curated_module_before['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP + assert curated_module_before['id'] == CURATED_MODULE_ID + assert curated_module_before['priorityExitShareThreshold'] == CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert curated_module_before['stakingModuleFee'] == CURATED_MODULE_OLD_MODULE_FEE_BP + assert curated_module_before['treasuryFee'] == CURATED_MODULE_OLD_TREASURY_FEE_BP + assert curated_module_before['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK + assert curated_module_before['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert curated_module_before['name'] == CURATED_MODULE_NAME + + # Item 1.2 + sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) + assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP + assert sdvt_module_before['id'] == SDVT_MODULE_ID + assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert sdvt_module_before['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + assert sdvt_module_before['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + assert sdvt_module_before['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert sdvt_module_before['name'] == SDVT_MODULE_NAME + + # Item 1.3 + a41_summary_before = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) + assert a41_summary_before['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_BEFORE + assert a41_summary_before['depositableValidatorsCount'] > 0 + assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" + + # Items 1.4,1.5 + trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() + trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() + assert trp_limit_before == TRP_LIMIT_BEFORE + assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS + assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - trp_already_spent_amount_before + assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP + assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP + + if details["status"] == PROPOSAL_STATUS["submitted"]: + chain.sleep(timelock.getAfterSubmitDelay() + 1) + dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + + if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: + chain.sleep(timelock.getAfterScheduleDelay() + 1) + dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + display_dg_events(dg_tx) + dg_events = group_dg_events_from_receipt( + dg_tx, + timelock=EMERGENCY_PROTECTED_TIMELOCK, + admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + ) + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + + # validate all DG events + validate_staking_module_update_event( + event=dg_events[0], + module_item=StakingModuleItem( + id=CURATED_MODULE_ID, + name=CURATED_MODULE_NAME, + address=None, + target_share=CURATED_MODULE_TARGET_SHARE_BP, + module_fee=CURATED_MODULE_NEW_MODULE_FEE_BP, + treasury_fee=CURATED_MODULE_NEW_TREASURY_FEE_BP, + priority_exit_share=CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + emitted_by=STAKING_ROUTER + ) + validate_staking_module_update_event( + event=dg_events[1], + module_item=StakingModuleItem( + id=SDVT_MODULE_ID, + name=SDVT_MODULE_NAME, + address=None, + target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, + module_fee=SDVT_MODULE_MODULE_FEE_BP, + treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, + priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + emitted_by=STAKING_ROUTER + ) + validate_target_validators_count_changed_event( + event=dg_events[2], + t=A41_TARGET_CHANGE_REQUEST, + emitted_by=CURATED_MODULE, + ) + validate_set_spent_amount_event( + dg_events[3], + new_spent_amount=0, + emitted_by=ET_TRP_REGISTRY, + ) + validate_set_limit_parameter_event( + dg_events[4], + limit=TRP_LIMIT_AFTER, + period_duration_month=TRP_PERIOD_DURATION_MONTHS, + period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, + emitted_by=ET_TRP_REGISTRY, + ) + + # ========================================================================= + # ==================== After DG proposal executed checks ================== + # ========================================================================= + + # Item 1.1 + curated_module_after = staking_router.getStakingModule(CURATED_MODULE_ID) + assert curated_module_after['stakingModuleFee'] == CURATED_MODULE_NEW_MODULE_FEE_BP + assert curated_module_after['treasuryFee'] == CURATED_MODULE_NEW_TREASURY_FEE_BP + assert curated_module_after['id'] == CURATED_MODULE_ID + assert curated_module_after['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP + assert curated_module_after['priorityExitShareThreshold'] == CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert curated_module_after['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK + assert curated_module_after['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert curated_module_after['name'] == CURATED_MODULE_NAME + # additional checks to make sure no other fields were changed (if before state is available) + if curated_module_before is not None: + assert curated_module_after['id'] == curated_module_before['id'] + assert curated_module_after['stakingModuleAddress'] == curated_module_before['stakingModuleAddress'] + assert curated_module_after['stakeShareLimit'] == curated_module_before['stakeShareLimit'] + assert curated_module_after['status'] == curated_module_before['status'] + assert curated_module_after['name'] == curated_module_before['name'] + assert curated_module_after['lastDepositAt'] == curated_module_before['lastDepositAt'] + assert curated_module_after['lastDepositBlock'] == curated_module_before['lastDepositBlock'] + assert curated_module_after['exitedValidatorsCount'] == curated_module_before['exitedValidatorsCount'] + assert curated_module_after['maxDepositsPerBlock'] == curated_module_before['maxDepositsPerBlock'] + assert curated_module_after['minDepositBlockDistance'] == curated_module_before['minDepositBlockDistance'] + assert curated_module_after['priorityExitShareThreshold'] == curated_module_before['priorityExitShareThreshold'] + assert len(curated_module_after.items()) == len(curated_module_before.items()) + assert len(curated_module_after.items()) == 13 + + # Item 1.2 + sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) + assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP + assert sdvt_module_after['id'] == SDVT_MODULE_ID + assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert sdvt_module_after['name'] == SDVT_MODULE_NAME + # additional checks to make sure no other fields were changed (if before state is available) + if sdvt_module_before is not None: + assert sdvt_module_after['id'] == sdvt_module_before['id'] + assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] + assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] + assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] + assert sdvt_module_after['status'] == sdvt_module_before['status'] + assert sdvt_module_after['name'] == sdvt_module_before['name'] + assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] + assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] + assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] + assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] + assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] + assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] + assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) + assert len(sdvt_module_after.items()) == 13 + + # Item 1.3 + a41_summary_after = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) + assert a41_summary_after['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_AFTER + assert a41_summary_after['depositableValidatorsCount'] == 0 + assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" + # additional checks to make sure no other fields were changed (if before state is available) + if a41_summary_before is not None: + assert a41_summary_after['targetValidatorsCount'] == a41_summary_before['targetValidatorsCount'] + assert a41_summary_after['stuckValidatorsCount'] == a41_summary_before['stuckValidatorsCount'] + assert a41_summary_after['refundedValidatorsCount'] == a41_summary_before['refundedValidatorsCount'] + assert a41_summary_after['stuckPenaltyEndTimestamp'] == a41_summary_before['stuckPenaltyEndTimestamp'] + assert a41_summary_after['totalExitedValidators'] == a41_summary_before['totalExitedValidators'] + assert a41_summary_after['totalDepositedValidators'] == a41_summary_before['totalDepositedValidators'] + assert len(a41_summary_after.items()) == 8 + + # Items 1.4,1.5 + trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() + trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() + assert trp_limit_after == TRP_LIMIT_AFTER + assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS + assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER + assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP + assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP + + # scenraio test for TRP ET factory behavior after the vote + trp_limit_test(stranger) + + +def trp_limit_test(stranger): + + easy_track = interface.EasyTrack(EASY_TRACK) + ldo_token = interface.ERC20(LDO_TOKEN) + to_spend = TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + max_spend_at_once = 5_000_000 * 10**18 + trp_committee_account = accounts.at(TRP_COMMITTEE, force=True) + + chain.snapshot() + + # check that there is no way to spend more then expected + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + [trp_committee_account], + [to_spend + 1], + stranger, + ) + + # spend all in several transfers + recipients = [] + amounts = [] + while to_spend > 0: + recipients.append(trp_committee_account) + amounts.append(min(max_spend_at_once, to_spend)) + to_spend -= min(max_spend_at_once, to_spend) + + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + recipients, + amounts, + stranger, + ) + + # make sure there is nothing left so that you can't spend anymore + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + [trp_committee_account], + [1], + stranger, + ) + + chain.revert() + +def et_limit_test(stranger, token, max_spend_at_once, to_spend, TRUSTED_CALLER, TOP_UP_ALLOWED_RECIPIENTS_FACTORY): + + easy_track = interface.EasyTrack(EASY_TRACK) + trusted_caller_account = accounts.at(TRUSTED_CALLER, force=True) + + chain.snapshot() + + # check that there is no way to spend more then expected + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [to_spend + 1], + stranger, + ) + + # spend all in several transfers + recipients = [] + amounts = [] + while to_spend > 0: + recipients.append(trusted_caller_account) + amounts.append(min(max_spend_at_once, to_spend)) + to_spend -= min(max_spend_at_once, to_spend) + + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + recipients, + amounts, + stranger, + ) + + # make sure there is nothing left so that you can't spend anymore + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [1], + stranger, + ) + + chain.revert() + + +def finance_limit_test(stranger, token, to_spend, decimals, TRUSTED_CALLER, TOP_UP_ALLOWED_RECIPIENTS_FACTORY, ALLOWED_RECIPIENTS_REGISTRY): + + easy_track = interface.EasyTrack(EASY_TRACK) + trusted_caller_account = accounts.at(TRUSTED_CALLER, force=True) + + chain.snapshot() + + # for Finance limit check - we first raise ET limits to 10 x finance_limit to be able to spend via Finance + interface.AllowedRecipientRegistry(ALLOWED_RECIPIENTS_REGISTRY).setLimitParameters( + (to_spend / (10**decimals) * 10**18) * 10, # 10 x finance_limit + 3, # 3 months + {"from": AGENT} + ) + + # check that there is no way to spend more then expected + with reverts("APP_AUTH_FAILED"): + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [to_spend + 1], + stranger, + ) + + # spend the allowed balance + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [to_spend], + stranger, + ) + + chain.revert() + + +def usds_wrap_happy_path(stranger): + USDC_FOR_TRANSFER = 1000 + USDS_TOKEN = "0xdC035D45d973E3EC169d2276DDab16f1e407384F" + + easy_track = interface.EasyTrack(EASY_TRACK) + usdc = interface.Usdc(USDC_TOKEN) + psmVariant1Actions = interface.PSMVariant1Actions(PSM_VARIANT1_ACTIONS) + usds_token = interface.Usds(USDS_TOKEN) + susds_token = interface.Susds(SUSDS_TOKEN) + + eoa = accounts[0] + + chain.snapshot() + + initial_susds_agent_balance = susds_token.balanceOf(AGENT) + + # fund EOA with USDC from Treasury + interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).addRecipient( + eoa.address, + "EOA_test", + {"from": AGENT} + ) + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + usdc, + [eoa], + [USDC_FOR_TRANSFER * 10**6], + stranger, + ) + assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 + assert usds_token.balanceOf(eoa.address) == 0 + assert susds_token.balanceOf(eoa.address) == 0 + + # wrap USDC to sUSDS via PSM + usdc.approve(PSM_VARIANT1_ACTIONS, USDC_FOR_TRANSFER * 10**6, {"from": eoa}) + psmVariant1Actions.swapAndDeposit(eoa.address, USDC_FOR_TRANSFER * 10**6, USDC_FOR_TRANSFER * 10**18, {"from": eoa}) + assert usdc.balanceOf(eoa.address) == 0 + assert usds_token.balanceOf(eoa.address) == 0 + susds_balance = susds_token.balanceOf(eoa.address) + assert susds_balance <= USDC_FOR_TRANSFER * 10**18 + assert susds_balance >= USDC_FOR_TRANSFER * 10**18 * 0.9 + + # send sUSDS back to Treasury + susds_token.transfer(AGENT, susds_balance, {"from": eoa}) + assert susds_token.balanceOf(eoa.address) == 0 + assert susds_token.balanceOf(AGENT) == susds_balance + initial_susds_agent_balance + print("swapped", USDC_FOR_TRANSFER, "USDC to", susds_balance / 10**18, "sUSDS") + + # send sUSDS again to EOA via Easy Track payment from Treasury + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + susds_token, + [eoa], + [susds_balance], + stranger, + ) + assert susds_token.balanceOf(eoa.address) == susds_balance + assert susds_token.balanceOf(AGENT) == initial_susds_agent_balance + + # wait 1 year to accumulate interest on sUSDS + chain.sleep(365 * 24 * 3600) + chain.mine() + susds_token.drip({"from": eoa}) + INTEREST_RATE = 0.04 + + # unwrap sUSDS to USDC + susds_token.approve(PSM_VARIANT1_ACTIONS, susds_balance, {"from": eoa}) + psmVariant1Actions.withdrawAndSwap(eoa.address, USDC_FOR_TRANSFER * 10**6 * (1 + INTEREST_RATE), USDC_FOR_TRANSFER * 10**18 * (1 + INTEREST_RATE), {"from": eoa}) + usdc_balance = usdc.balanceOf(eoa.address) + print("swapped", susds_balance / 10**18, "sUSDS to", usdc_balance / 10**6, "USDC, leftover:", susds_token.balanceOf(eoa.address) / 10**18, "sUSDS") + assert susds_token.balanceOf(eoa.address) < 5.0 * 10**18 # leftover from interest surplus + assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 * (1 + INTEREST_RATE) + + chain.revert() + + +def prepare_agent_for_dai_payment(amount: int): + agent, dai = interface.Agent(AGENT), interface.Dai(DAI_TOKEN) + if dai.balanceOf(agent) < amount: + dai_ward_impersonated = accounts.at("0x9759A6Ac90977b93B58547b4A71c78317f391A28", force=True) + dai.mint(agent, amount, {"from": dai_ward_impersonated}) + + assert dai.balanceOf(agent) >= amount, f"Insufficient DAI balance" + + +def prepare_agent_for_usdc_payment(amount: int): + agent, usdc = interface.Agent(AGENT), interface.Usdc(USDC_TOKEN) + if usdc.balanceOf(agent) < amount: + usdc_minter = accounts.at("0x5B6122C109B78C6755486966148C1D70a50A47D7", force=True) + usdc_controller = accounts.at("0x79E0946e1C186E745f1352d7C21AB04700C99F71", force=True) + usdc_master_minter = interface.UsdcMasterMinter("0xE982615d461DD5cD06575BbeA87624fda4e3de17") + usdc_master_minter.incrementMinterAllowance(amount, {"from": usdc_controller}) + usdc.mint(agent, amount, {"from": usdc_minter}) + + assert usdc.balanceOf(agent) >= amount, "Insufficient USDC balance" + + +def prepare_agent_for_usdt_payment(amount: int): + agent, usdt = interface.Agent(AGENT), interface.Usdt(USDT_TOKEN) + if usdt.balanceOf(agent) < amount: + usdt_owner = accounts.at("0xC6CDE7C39eB2f0F0095F41570af89eFC2C1Ea828", force=True) + usdt.issue(amount, {"from": usdt_owner}) + usdt.transfer(agent, amount, {"from": usdt_owner}) + + assert usdt.balanceOf(agent) >= amount, "Insufficient USDT balance" + + +def prepare_agent_for_susds_payment(amount: int): + agent, susds = interface.Agent(AGENT), interface.ERC20(SUSDS_TOKEN) + if susds.balanceOf(agent) < amount: + susds_whale = accounts.at("0xBc65ad17c5C0a2A4D159fa5a503f4992c7B545FE", force=True) + susds.transfer(agent, amount, {"from": susds_whale}) + + assert susds.balanceOf(agent) >= amount, "Insufficient sUSDS balance" + + +def prepare_agent_for_ldo_payment(amount: int): + agent, ldo = interface.Agent(AGENT), interface.ERC20(LDO_TOKEN) + assert ldo.balanceOf(agent) >= amount, "Insufficient LDO balance 🫡" + + +def prepare_agent_for_steth_payment(amount: int): + STETH_TRANSFER_MAX_DELTA = 2 + + agent, steth = interface.Agent(AGENT), interface.Lido(STETH_TOKEN) + eth_whale = accounts.at("0x00000000219ab540356cBB839Cbe05303d7705Fa", force=True) + if steth.balanceOf(agent) < amount: + steth.submit(ZERO_ADDRESS, {"from": eth_whale, "value": amount + 2 * STETH_TRANSFER_MAX_DELTA}) + steth.transfer(agent, amount + STETH_TRANSFER_MAX_DELTA, {"from": eth_whale}) + assert steth.balanceOf(agent) >= amount, "Insufficient stETH balance" From 64d323a78ab657d0a1af645bb8adffed94754ec6 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 4 Dec 2025 15:31:42 +0000 Subject: [PATCH 092/178] ga fixes --- scripts/upgrade_2025_12_10_mainnet_v3.py | 2 +- scripts/vote_2025_12_10.py | 10 +++++----- .../test_node_operators_registry.py | 7 ++++--- tests/utils_test_2025_12_10_operations.py | 19 +++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index 4275f6690..de356336d 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -1,7 +1,7 @@ """ # Vote 2025_12_10 -=== 1. DG PROPOPSAL === +=== 1. DG PROPOSAL === 1.1. Ensure DG proposal execution is within daily time window (14:00 UTC - 23:00 UTC) 1.2. Call V3Template.startUpgrade 1.3. Upgrade LidoLocator implementation diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index ebee280ab..b1dfb85d2 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -1,7 +1,7 @@ """ # Vote 2025_12_10 -=== 1. DG PROPOPSAL === +=== 1. DG PROPOSAL === I. Change Curated Module fees 1.1. Change Curated Module (MODULE_ID = 1) fees in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999: Module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP @@ -17,7 +17,7 @@ === NON-DG ITEMS === V. Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance -2. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e +2. Temporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e 3. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca 4. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e 5. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 @@ -88,7 +88,7 @@ class TokenLimit(NamedTuple): SDVT_MODULE_ID = 2 SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP = 478 SDVT_MODULE_MODULE_FEE_BP = 800 SDVT_MODULE_TREASURY_FEE_BP = 200 SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 @@ -227,7 +227,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: staking_router.updateStakingModule.encode_input( SDVT_MODULE_ID, SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP, SDVT_MODULE_MODULE_FEE_BP, SDVT_MODULE_TREASURY_FEE_BP, SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, @@ -266,7 +266,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: dg_call_script[0] ), ( - "2. Termporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", + "2. Temporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", ( stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.grantRole.encode_input( convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), diff --git a/tests/acceptance/test_node_operators_registry.py b/tests/acceptance/test_node_operators_registry.py index 794da4ab6..c9f4ac0d0 100644 --- a/tests/acceptance/test_node_operators_registry.py +++ b/tests/acceptance/test_node_operators_registry.py @@ -114,8 +114,9 @@ def test_nor_state(contract): assert node_operator["totalVettedValidators"] <= node_operator["totalAddedValidators"] node_operator_summary = contract.getNodeOperatorSummary(id) - exited_node_operators = [12, 1, 32] # NO id 12 was added on vote 23-05-23, NO id 1 was added on vote 03-10-23, NO id 32 was added on vote 10-12-25 - assert node_operator_summary["targetLimitMode"] == (1 if id in exited_node_operators else 0) + exited_node_operators = [12, 1] # NO id 12 was added on vote 23-05-23, NO id 1 was added on vote 03-10-23 + soft_limit_0_node_operators = [32] # NO id 32 was added on vote 10-12-25 + assert node_operator_summary["targetLimitMode"] == (1 if id in exited_node_operators or id in soft_limit_0_node_operators else 0) assert node_operator_summary["targetValidatorsCount"] == 0 # Can be more than 0 in regular protocol operations # assert node_operator_summary["stuckValidatorsCount"] == 0 @@ -133,7 +134,7 @@ def test_nor_state(contract): assert node_operator["totalExitedValidators"] == node_operator_summary["totalExitedValidators"] assert node_operator["totalDepositedValidators"] == node_operator_summary["totalDepositedValidators"] - if id in exited_node_operators: + if id in soft_limit_0_node_operators: assert node_operator_summary["depositableValidatorsCount"] == 0 else: no_depositable_validators_count = ( diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py index 5a215a128..ed682380c 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_10_operations.py @@ -88,7 +88,6 @@ class TokenLimit(NamedTuple): LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" -DEV_GAS_STORE = "0x7FEa69d107A77B5817379d1254cc80D9671E171b" PSM_VARIANT1_ACTIONS = "0xd0A61F2963622e992e6534bde4D52fd0a89F39E0" @@ -113,7 +112,8 @@ class TokenLimit(NamedTuple): SDVT_MODULE_ID = 2 SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_OLD_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP = 478 SDVT_MODULE_MODULE_FEE_BP = 800 SDVT_MODULE_TREASURY_FEE_BP = 200 SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 @@ -125,7 +125,7 @@ class TokenLimit(NamedTuple): MATIC_IN_LIDO_LABS_BEFORE = 0 MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 -TRP_LIMIT_BEFORE = 9_178_284.42 * 10**18 +TRP_LIMIT_BEFORE = 9_178_284_420 * 10**15 # == 9_178_284.42 * 10**18 TRP_ALREADY_SPENT_AFTER = 0 TRP_LIMIT_AFTER = 15_000_000 * 10**18 TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC @@ -363,7 +363,7 @@ def dual_governance_proposal_calls(): staking_router.updateStakingModule.encode_input( SDVT_MODULE_ID, SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP, SDVT_MODULE_MODULE_FEE_BP, SDVT_MODULE_TREASURY_FEE_BP, SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, @@ -602,9 +602,9 @@ def enact_and_test_voting( matic_labs_balance_after = matic_token.balanceOf(LOL_MS) assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER # make sure LOL can actually spend the received MATIC - matic_token.transfer(DEV_GAS_STORE, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) + matic_token.transfer(stranger.address, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 - assert matic_token.balanceOf(DEV_GAS_STORE) == MATIC_IN_LIDO_LABS_AFTER / 2 + assert matic_token.balanceOf(stranger.address) == MATIC_IN_LIDO_LABS_AFTER / 2 assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT @@ -807,7 +807,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP assert sdvt_module_before['id'] == SDVT_MODULE_ID - assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_OLD_PRIORITY_EXIT_THRESHOLD_BP assert sdvt_module_before['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP assert sdvt_module_before['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP assert sdvt_module_before['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK @@ -867,7 +867,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, module_fee=SDVT_MODULE_MODULE_FEE_BP, treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, - priority_exit_share=SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + priority_exit_share=SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP), emitted_by=STAKING_ROUTER ) validate_target_validators_count_changed_event( @@ -922,7 +922,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP assert sdvt_module_after['id'] == SDVT_MODULE_ID - assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK @@ -941,7 +941,6 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] - assert sdvt_module_after['priorityExitShareThreshold'] == sdvt_module_before['priorityExitShareThreshold'] assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) assert len(sdvt_module_after.items()) == 13 From 44c7193473e904c1cf8e54e35931616f5fea233b Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 4 Dec 2025 15:38:10 +0000 Subject: [PATCH 093/178] ga fixes ii --- scripts/vote_2025_12_10.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index b1dfb85d2..778127d62 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -6,7 +6,7 @@ 1.1. Change Curated Module (MODULE_ID = 1) fees in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999: Module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP II. Raise SDVT module stake share limit -1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 +1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP and priority exit threshold from 444 BP to 478 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 III. Set A41 soft target validator limit to 0 1.3. Set soft-mode target validators limit to 0 for Node operator A41 (ID = 32) in Curated Module (MODULE_ID = 1) in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 @@ -221,7 +221,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), agent_forward([ - # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP and priority exit threshold from 444 BP to 478 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 ( staking_router.address, staking_router.updateStakingModule.encode_input( From f43b7d51310db8da9db7d1c999d8b20ea2956aa3 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 4 Dec 2025 16:22:25 +0000 Subject: [PATCH 094/178] fixed sdvt values --- configs/config_mainnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 78bf65a91..61846cbf7 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -162,7 +162,7 @@ SIMPLE_DVT_ARAGON_APP_NAME = "simple-dvt" SIMPLE_DVT_ARAGON_APP_ID = "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4" SIMPLE_DVT_MODULE_STUCK_PENALTY_DELAY = 0 -SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 400 +SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 430 SIMPLE_DVT_MODULE_MODULE_FEE_BP = 800 SIMPLE_DVT_MODULE_TREASURY_FEE_BP = 200 SIMPLE_DVT_MODULE_ID = 2 @@ -172,7 +172,7 @@ "0x637572617465642d6f6e636861696e2d76310000000000000000000000000000" ) SIMPLE_DVT_VERSION = 4 -SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 444 +SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 478 SIMPLE_DVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SIMPLE_DVT_MODULE_MIN_DEPOSITS_BLOCK_DISTANCE = 25 From da19efc08c80ebd0a06638925885b5775dde326d Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 4 Dec 2025 16:53:12 +0000 Subject: [PATCH 095/178] feat: add helper funcs for orderly vote enactment for regression tests --- tests/acceptance/conftest.py | 5 +++-- utils/import_current_votes.py | 6 ++++-- utils/test/governance_helpers.py | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index 709aa2f9d..d4479c5a0 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -1,7 +1,7 @@ import pytest from utils.import_current_votes import is_there_any_vote_scripts, is_there_any_upgrade_scripts -from utils.test.governance_helpers import execute_vote_and_process_dg_proposals +from utils.test.governance_helpers import execute_vote_and_process_dg_proposals_orderly # execute_vote_and_process_dg_proposals from utils.dual_governance import is_there_any_proposals_from_env, process_pending_proposals @@ -12,4 +12,5 @@ def autoexecute_dg_proposals(): @pytest.fixture(scope="module", autouse=is_there_any_vote_scripts() or is_there_any_upgrade_scripts() or is_there_any_proposals_from_env()) def autoexecute_vote(module_isolation, helpers, vote_ids_from_env, dg_proposal_ids_from_env): - execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env) + #execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env) + execute_vote_and_process_dg_proposals_orderly(helpers) diff --git a/utils/import_current_votes.py b/utils/import_current_votes.py index 73005f71d..71466fca4 100644 --- a/utils/import_current_votes.py +++ b/utils/import_current_votes.py @@ -42,7 +42,7 @@ def is_there_any_upgrade_scripts() -> bool: return len(get_upgrade_script_files()) > 0 -def start_and_execute_votes(dao_voting, helpers) -> tuple[List[str], List[TransactionReceipt]]: +def start_and_execute_votes(dao_voting, helpers, vote_file_index = None) -> tuple[List[str], List[TransactionReceipt]]: vote_files = get_vote_script_files() upgrade_files = get_upgrade_script_files() vote_files.extend(upgrade_files) @@ -50,7 +50,9 @@ def start_and_execute_votes(dao_voting, helpers) -> tuple[List[str], List[Transa vote_ids = [] vote_transactions = [] - for vote_file in sorted(vote_files): + for current_vote_file_index, vote_file in enumerate(sorted(vote_files)): + if vote_file_index is not None and current_vote_file_index != vote_file_index: + continue script_name = os.path.splitext(os.path.basename(vote_file))[0] print(f"Starting voting from script '{script_name}'...") name_for_import = "scripts." + script_name diff --git a/utils/test/governance_helpers.py b/utils/test/governance_helpers.py index 497405e30..a49b1c53d 100644 --- a/utils/test/governance_helpers.py +++ b/utils/test/governance_helpers.py @@ -25,3 +25,24 @@ def execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposa return new_proposal_ids = list(range(proposals_count_before + 1, proposals_count_after + 1)) process_proposals(new_proposal_ids) + + +def execute_vote_and_process_dg_proposals_orderly(helpers): + + # V1 + proposals_count_before1 = contracts.emergency_protected_timelock.getProposalsCount() + start_and_execute_votes(contracts.voting, helpers, 0) + proposals_count_after1 = contracts.emergency_protected_timelock.getProposalsCount() + new_proposal_ids1 = list(range(proposals_count_before1 + 1, proposals_count_after1 + 1)) + + # DG1 + process_proposals(new_proposal_ids1) + + # V2 + proposals_count_before2 = contracts.emergency_protected_timelock.getProposalsCount() + start_and_execute_votes(contracts.voting, helpers, 1) + proposals_count_after2 = contracts.emergency_protected_timelock.getProposalsCount() + new_proposal_ids2 = list(range(proposals_count_before2 + 1, proposals_count_after2 + 1)) + + # DG2 + process_proposals(new_proposal_ids2) From e46e80bd7d8f85c8f139d80f7fe4ee64cad4075e Mon Sep 17 00:00:00 2001 From: Nikita P Date: Thu, 4 Dec 2025 16:55:40 +0000 Subject: [PATCH 096/178] fix: refactoring --- tests/acceptance/conftest.py | 5 ++--- utils/test/governance_helpers.py | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py index d4479c5a0..709aa2f9d 100644 --- a/tests/acceptance/conftest.py +++ b/tests/acceptance/conftest.py @@ -1,7 +1,7 @@ import pytest from utils.import_current_votes import is_there_any_vote_scripts, is_there_any_upgrade_scripts -from utils.test.governance_helpers import execute_vote_and_process_dg_proposals_orderly # execute_vote_and_process_dg_proposals +from utils.test.governance_helpers import execute_vote_and_process_dg_proposals from utils.dual_governance import is_there_any_proposals_from_env, process_pending_proposals @@ -12,5 +12,4 @@ def autoexecute_dg_proposals(): @pytest.fixture(scope="module", autouse=is_there_any_vote_scripts() or is_there_any_upgrade_scripts() or is_there_any_proposals_from_env()) def autoexecute_vote(module_isolation, helpers, vote_ids_from_env, dg_proposal_ids_from_env): - #execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env) - execute_vote_and_process_dg_proposals_orderly(helpers) + execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env) diff --git a/utils/test/governance_helpers.py b/utils/test/governance_helpers.py index a49b1c53d..76ea20a7c 100644 --- a/utils/test/governance_helpers.py +++ b/utils/test/governance_helpers.py @@ -11,23 +11,23 @@ def execute_vote(helpers, vote_ids_from_env): start_and_execute_votes(contracts.voting, helpers) -def execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env): - if vote_ids_from_env and dg_proposal_ids_from_env: - execute_vote(helpers, vote_ids_from_env) - process_proposals(dg_proposal_ids_from_env) - elif not vote_ids_from_env and dg_proposal_ids_from_env: - process_proposals(dg_proposal_ids_from_env) - else: - proposals_count_before = contracts.emergency_protected_timelock.getProposalsCount() - execute_vote(helpers, vote_ids_from_env) - proposals_count_after = contracts.emergency_protected_timelock.getProposalsCount() - if proposals_count_after == proposals_count_before: - return - new_proposal_ids = list(range(proposals_count_before + 1, proposals_count_after + 1)) - process_proposals(new_proposal_ids) +#def execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env): +# if vote_ids_from_env and dg_proposal_ids_from_env: +# execute_vote(helpers, vote_ids_from_env) +# process_proposals(dg_proposal_ids_from_env) +# elif not vote_ids_from_env and dg_proposal_ids_from_env: +# process_proposals(dg_proposal_ids_from_env) +# else: +# proposals_count_before = contracts.emergency_protected_timelock.getProposalsCount() +# execute_vote(helpers, vote_ids_from_env) +# proposals_count_after = contracts.emergency_protected_timelock.getProposalsCount() +# if proposals_count_after == proposals_count_before: +# return +# new_proposal_ids = list(range(proposals_count_before + 1, proposals_count_after + 1)) +# process_proposals(new_proposal_ids) -def execute_vote_and_process_dg_proposals_orderly(helpers): +def execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env): # V1 proposals_count_before1 = contracts.emergency_protected_timelock.getProposalsCount() From d19c4f0f8227394f831c768dab1affd3965c5571 Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 4 Dec 2025 20:35:41 +0300 Subject: [PATCH 097/178] test: fix snapshot tests --- tests/snapshot/test_dsm.py | 2 +- tests/snapshot/test_first_slots.py | 6 +++++- tests/snapshot/test_lido_snapshot.py | 1 + tests/snapshot/test_voting.py | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/snapshot/test_dsm.py b/tests/snapshot/test_dsm.py index 6de8a2ed9..4d3df33b3 100644 --- a/tests/snapshot/test_dsm.py +++ b/tests/snapshot/test_dsm.py @@ -223,7 +223,7 @@ def far_block() -> int: @pytest.fixture(scope="module") def far_ts() -> int: - return chain.time() + 14 * 24 * 60 * 60 # 14 days + return chain.time() + 30 * 24 * 60 * 60 # 30 days def _sleep_till_block(block: int, ts: int) -> None: diff --git a/tests/snapshot/test_first_slots.py b/tests/snapshot/test_first_slots.py index ea8bce27a..44754c252 100644 --- a/tests/snapshot/test_first_slots.py +++ b/tests/snapshot/test_first_slots.py @@ -51,6 +51,10 @@ def skip_slots() -> Sequence[tuple[str, int]]: return [ # adding 9 new easy track factories (contracts.easy_track.address, 0x05), + # new slot in the contract _nodeOperatorSummary + (contracts.node_operators_registry.address, 0x01), + # finance slot changing due to deposit bot funding / limits updates + (contracts.finance.address, 0x07), ] @@ -156,7 +160,7 @@ def far_block() -> int: @pytest.fixture(scope="module") def far_ts() -> int: - return chain.time() + 14 * 24 * 60 * 60 # 14 days + return chain.time() + 30 * 24 * 60 * 60 # 30 days def _sleep_till_block(block: int, ts: int) -> None: diff --git a/tests/snapshot/test_lido_snapshot.py b/tests/snapshot/test_lido_snapshot.py index 91361d7d5..bad325caf 100644 --- a/tests/snapshot/test_lido_snapshot.py +++ b/tests/snapshot/test_lido_snapshot.py @@ -43,6 +43,7 @@ class Frame(TypedDict): "lido.Lido.bufferedEther": ZERO_BYTES32, "lido.Lido.depositedValidators": ZERO_BYTES32, "lido.StETH.totalShares": ZERO_BYTES32, + "getFeeDistribution": ((5165, 0, 4834), (3783, 0, 6216)), } def test_lido_no_changes_in_views(sandwich_upgrade: SandwichFn): diff --git a/tests/snapshot/test_voting.py b/tests/snapshot/test_voting.py index 905bfeaba..d6ea0d7ba 100644 --- a/tests/snapshot/test_voting.py +++ b/tests/snapshot/test_voting.py @@ -110,7 +110,7 @@ def test_create_wait_enact(helpers, vote_time, call_target, vote_ids_from_env, d for step_name, diff in step_diffs.items(): if not vote_ids_from_env: assert_expected_diffs( - step_name, diff, {"votesLength": ValueChanged(from_val=votesLength + 1, to_val=votesLength + 2)} + step_name, diff, {"votesLength": ValueChanged(from_val=votesLength + 1, to_val=votesLength + 3)} ) assert_no_diffs(step_name, diff) From 0ed55e1615c58a127c0de090b54768ea7ff3fb24 Mon Sep 17 00:00:00 2001 From: dry914 Date: Thu, 4 Dec 2025 23:01:11 +0300 Subject: [PATCH 098/178] test: try to fix permissions timeout --- tests/regression/test_permissions.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/regression/test_permissions.py b/tests/regression/test_permissions.py index ed1436c59..4152e4c9c 100644 --- a/tests/regression/test_permissions.py +++ b/tests/regression/test_permissions.py @@ -706,6 +706,17 @@ def get_http_w3_provider_url(): assert False, 'Web3 HTTP Provider token env var not found' +def get_http_provider_timeout(): + """ + HTTP provider timeout in seconds for remote RPC calls. + Can be overridden via WEB3_HTTP_PROVIDER_TIMEOUT env var. + """ + if os.getenv("WEB3_HTTP_PROVIDER_TIMEOUT") is not None: + return float(os.getenv("WEB3_HTTP_PROVIDER_TIMEOUT")) + # use higher default than requests' 10s to reduce flaky ReadTimeouts in CI + return 60.0 + + def get_max_log_range(): if os.getenv("MAX_GET_LOGS_RANGE") is not None: return int(os.getenv("MAX_GET_LOGS_RANGE")) @@ -714,7 +725,12 @@ def get_max_log_range(): def active_aragon_roles(protocol_permissions): local_rpc_provider = web3 - remote_rpc_provider = Web3(Web3.HTTPProvider(get_http_w3_provider_url())) + remote_rpc_provider = Web3( + Web3.HTTPProvider( + get_http_w3_provider_url(), + request_kwargs={"timeout": get_http_provider_timeout()}, + ) + ) max_range = get_max_log_range() event_signature_hash = remote_rpc_provider.keccak(text="SetPermission(address,address,bytes32,bool)").hex() From 4f06df7550a10f2f3afa98ec3f5b2aa4401e1604 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 5 Dec 2025 08:21:03 +0000 Subject: [PATCH 099/178] fix: TRP reset rem --- scripts/vote_2025_12_10.py | 21 ++++++------------- tests/utils_test_2025_12_10_operations.py | 25 +++++++---------------- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index 778127d62..f081abbe2 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -11,9 +11,8 @@ III. Set A41 soft target validator limit to 0 1.3. Set soft-mode target validators limit to 0 for Node operator A41 (ID = 32) in Curated Module (MODULE_ID = 1) in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 -IV. Reset Easy Track TRP limit -1.4. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO -1.5. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months +IV. Set Easy Track TRP limit +1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months === NON-DG ITEMS === V. Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance @@ -41,10 +40,7 @@ from utils.dual_governance import submit_proposals from utils.agent import agent_forward from utils.permissions import encode_permission_revoke, encode_permission_grant_p -from utils.allowed_recipients_registry import ( - unsafe_set_spent_amount, - set_limit_parameters, -) +from utils.allowed_recipients_registry import set_limit_parameters # ============================== Types =================================== @@ -96,7 +92,6 @@ class TokenLimit(NamedTuple): TRP_PERIOD_DURATION_MONTHS = 12 TRP_NEW_LIMIT = 15_000_000 * 10**18 -TRP_NEW_SPENT_AMOUNT = 0 MATIC_FOR_TRANSFER = 508_106 * 10**18 @@ -243,11 +238,7 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ) ]), agent_forward([ - # 1.4. Set spent amount for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 0 LDO - unsafe_set_spent_amount(spent_amount=TRP_NEW_SPENT_AMOUNT, registry_address=ET_TRP_REGISTRY), - ]), - agent_forward([ - # 1.5. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + # 1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months set_limit_parameters( limit=TRP_NEW_LIMIT, period_duration_months=TRP_PERIOD_DURATION_MONTHS, @@ -257,12 +248,12 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ] dg_call_script = submit_proposals([ - (dg_items, "Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, reset Easy Track TRP limit") + (dg_items, "Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, set Easy Track TRP limit") ]) vote_desc_items, call_script_items = zip( ( - "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, reset Easy Track TRP limit", + "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, set Easy Track TRP limit", dg_call_script[0] ), ( diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py index ed682380c..d69860d41 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_10_operations.py @@ -19,10 +19,7 @@ display_voting_events, display_dg_events ) -from utils.allowed_recipients_registry import ( - unsafe_set_spent_amount, - set_limit_parameters, -) +from utils.allowed_recipients_registry import set_limit_parameters from utils.test.event_validators.payout import ( validate_token_payout_event, Payout, @@ -126,7 +123,7 @@ class TokenLimit(NamedTuple): MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 TRP_LIMIT_BEFORE = 9_178_284_420 * 10**15 # == 9_178_284.42 * 10**18 -TRP_ALREADY_SPENT_AFTER = 0 +TRP_ALREADY_SPENT_AFTER = 4208709 * 10**18 TRP_LIMIT_AFTER = 15_000_000 * 10**18 TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC @@ -377,9 +374,6 @@ def dual_governance_proposal_calls(): staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE_AFTER, NEW_A41_TARGET_LIMIT), ) ]), - agent_forward([ - unsafe_set_spent_amount(spent_amount=0, registry_address=ET_TRP_REGISTRY), - ]), agent_forward([ set_limit_parameters( limit=TRP_LIMIT_AFTER, @@ -617,7 +611,7 @@ def enact_and_test_voting( proposal_id=EXPECTED_DG_PROPOSAL_ID, proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, reset Easy Track TRP limit", + metadata="Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, set Easy Track TRP limit", proposal_calls=dual_governance_proposal_calls(), ) @@ -769,7 +763,7 @@ def enact_and_test_voting( def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): - EXPECTED_DG_EVENTS_COUNT = 5 + EXPECTED_DG_EVENTS_COUNT = 4 # ======================================================================= # ========================= Arrange variables =========================== @@ -820,7 +814,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): assert a41_summary_before['depositableValidatorsCount'] > 0 assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" - # Items 1.4,1.5 + # Items 1.4 trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() assert trp_limit_before == TRP_LIMIT_BEFORE @@ -875,13 +869,8 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): t=A41_TARGET_CHANGE_REQUEST, emitted_by=CURATED_MODULE, ) - validate_set_spent_amount_event( - dg_events[3], - new_spent_amount=0, - emitted_by=ET_TRP_REGISTRY, - ) validate_set_limit_parameter_event( - dg_events[4], + dg_events[3], limit=TRP_LIMIT_AFTER, period_duration_month=TRP_PERIOD_DURATION_MONTHS, period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, @@ -959,7 +948,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): assert a41_summary_after['totalDepositedValidators'] == a41_summary_before['totalDepositedValidators'] assert len(a41_summary_after.items()) == 8 - # Items 1.4,1.5 + # Items 1.4 trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() assert trp_limit_after == TRP_LIMIT_AFTER From 15a72e06c96733e291f30f78e0ae09730a24334d Mon Sep 17 00:00:00 2001 From: dry914 Date: Fri, 5 Dec 2025 11:23:14 +0300 Subject: [PATCH 100/178] test: update docker file --- Dockerfile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Dockerfile b/Dockerfile index 44b4acc37..b461f66d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -250,6 +250,23 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then \ git clean -d -x -f; \ fi +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + # build solc-v0.8.21 + git checkout v0.8.21; \ + # the compiler throws warnings when compiling this version, and the warnings are treated as errors. + # we disable treating the warnings as errors, unless the build doesn't succeed + grep -rl '\-Werror' ./cmake/EthCompilerSettings.cmake | xargs sed -i 's/\-Werror/\-Wno\-error/g'; \ + # there is no sudo in the container, but we are under root so we do not need it + grep -rl 'sudo make install' ./scripts/build.sh | xargs sed -i 's/sudo make install/make install/g'; \ + # build solc faster + grep -rl 'make -j2' ./scripts/build.sh | xargs sed -i 's/make -j2/make -j4/g'; \ + ./scripts/build.sh; \ + mv /usr/local/bin/solc /root/.solcx/solc-v0.8.21; \ + git checkout .; \ + git checkout develop; \ + git clean -d -x -f; \ + fi + RUN if [ "$TARGETARCH" = "arm64" ]; then \ # build solc-v0.6.11 git checkout v0.6.11; \ @@ -321,6 +338,7 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.15 --version | g RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.19 --version | grep 'Version: 0.8.19+commit.7dd6d404' || (echo "Incorrect solc-v0.8.19 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.24 --version | grep 'Version: 0.8.24+commit.e11b9ed9' || (echo "Incorrect solc-v0.8.24 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.25 --version | grep 'Version: 0.8.25+commit.b61c2a91' || (echo "Incorrect solc-v0.8.25 version" && exit 1) fi +RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.21 --version | grep 'Version: 0.8.21+commit.d9974bed' || (echo "Incorrect solc-v0.8.21 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.6.11 --version | grep 'Version: 0.6.11+commit.5ef660b1' || (echo "Incorrect solc-v0.6.11 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.vvm/vyper-0.3.7 --version | grep '0.3.7+' || (echo "Incorrect vyper-0.3.7 version" && exit 1) fi From e590478ef0575e153eb497a423e07d1d12873a06 Mon Sep 17 00:00:00 2001 From: dry914 Date: Fri, 5 Dec 2025 14:34:00 +0300 Subject: [PATCH 101/178] tests: fix snap test --- tests/snapshot/test_lido_snapshot.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/snapshot/test_lido_snapshot.py b/tests/snapshot/test_lido_snapshot.py index bad325caf..da89f9f15 100644 --- a/tests/snapshot/test_lido_snapshot.py +++ b/tests/snapshot/test_lido_snapshot.py @@ -43,9 +43,14 @@ class Frame(TypedDict): "lido.Lido.bufferedEther": ZERO_BYTES32, "lido.Lido.depositedValidators": ZERO_BYTES32, "lido.StETH.totalShares": ZERO_BYTES32, - "getFeeDistribution": ((5165, 0, 4834), (3783, 0, 6216)), } + +IGNORED_SNAPSHOT_KEYS: set[str] = { + "getFeeDistribution", +} + + def test_lido_no_changes_in_views(sandwich_upgrade: SandwichFn): """Test that no views change during the upgrade process.""" @@ -474,6 +479,8 @@ def _stacks_equal(stacks: tuple[Stack, Stack]) -> None: with check: unexpected: dict[str, tuple[Any, Any]] = {} for key, before_val in v1_frame["snap"].items(): + if key in IGNORED_SNAPSHOT_KEYS: + continue after_val = v2_frame["snap"].get(key) if before_val == after_val: continue From bf5201a54a249b8debd04e469995588159414fc4 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Fri, 5 Dec 2025 12:15:48 +0000 Subject: [PATCH 102/178] feat: use latest contracts --- .github/workflows/core_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index cfa5f1c69..200e92aab 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -56,4 +56,4 @@ jobs: run: docker exec -e CORE_TESTS_TARGET_RPC_URL -e NETWORK_STATE_FILE tests-runner bash -c 'make test-core' env: CORE_TESTS_TARGET_RPC_URL: http://127.0.0.1:8546 - NETWORK_STATE_FILE: deployed-mainnet-v3.json + NETWORK_STATE_FILE: deployed-mainnet.json From 1a1b3742690b66b49f033a9adbb974a32b941d5a Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Fri, 5 Dec 2025 12:29:56 +0000 Subject: [PATCH 103/178] feat: restore dockerfile --- .github/workflows/core_tests.yml | 3 +-- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 200e92aab..702bf1564 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -53,7 +53,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run core tests - run: docker exec -e CORE_TESTS_TARGET_RPC_URL -e NETWORK_STATE_FILE tests-runner bash -c 'make test-core' + run: docker exec -e CORE_TESTS_TARGET_RPC_URL tests-runner bash -c 'make test-core' env: CORE_TESTS_TARGET_RPC_URL: http://127.0.0.1:8546 - NETWORK_STATE_FILE: deployed-mainnet.json diff --git a/Dockerfile b/Dockerfile index 21d35eb77..44b4acc37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nikolaik/python-nodejs:python3.10-nodejs24-slim +FROM nikolaik/python-nodejs:python3.10-nodejs18 USER root ARG TARGETARCH From ca873e453c5ca156a6ded25991567511dc8f8ced Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Fri, 5 Dec 2025 12:38:18 +0000 Subject: [PATCH 104/178] chore: add some polishing to makefile --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 388631453..50a39055c 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ define run_2nd_test poetry run $(1) endef +# Get the latest block number from the target RPC node to use as FORKING_BLOCK_NUMBER for core tests __get_rpc_latest_block_number: @curl -s -X POST $(CORE_TESTS_TARGET_RPC_URL) \ -H "Content-Type: application/json" \ @@ -86,11 +87,10 @@ node3: test-core: FORKING_BLOCK_NUMBER=$$($(MAKE) --no-print-directory __get_rpc_latest_block_number) && \ - cd $(CORE_DIR) && \ echo "FORKING_BLOCK_NUMBER: $$FORKING_BLOCK_NUMBER" && \ + cd $(CORE_DIR) && \ RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ - FORKING_BLOCK_NUMBER=$$FORKING_BLOCK_NUMBER \ yarn test:integration slots: From b2c1789806788297ccf75049ee4204cac7936c19 Mon Sep 17 00:00:00 2001 From: hweawer Date: Fri, 5 Dec 2025 14:17:24 +0100 Subject: [PATCH 105/178] Fix prevExitRequestLimit comparison in gateseal twg test --- tests/regression/test_gate_seal.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/regression/test_gate_seal.py b/tests/regression/test_gate_seal.py index f11a3c486..e0425b9fe 100644 --- a/tests/regression/test_gate_seal.py +++ b/tests/regression/test_gate_seal.py @@ -298,15 +298,27 @@ def test_gate_seal_twg_veb_scenario(steth_holder, gate_seal_committee, eth_whale (_,_,_, vebInitLimit1, vebInitLimit2) = contracts.validators_exit_bus_oracle.getExitRequestLimitFullInfo() contracts.validators_exit_bus_oracle.submitExitRequestsHash(hash, {"from": "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977"}) contracts.validators_exit_bus_oracle.submitExitRequestsData((report[4], 1), {"from": submitter}) - (_,_,_, twgInitLimit1, twgInitLimit2) = contracts.triggerable_withdrawals_gateway.getExitRequestLimitFullInfo() + ( + twgInitMaxExitRequestLimit1, + twgInitExitsPerFrameLimit1, + twgInitFrameDurationInSeconds1, + twgInitPrevExitRequestsLimit1, + twgInitCurrentRequestLimit2 + ) = contracts.triggerable_withdrawals_gateway.getExitRequestLimitFullInfo() tx = contracts.validators_exit_bus_oracle.triggerExits((report[4], 1), [0], steth_holder, {"from": steth_holder, 'value': value}) - (_,_,_, twgLimit1, twgLimit2) = contracts.triggerable_withdrawals_gateway.getExitRequestLimitFullInfo() + ( + twgMaxExitRequestLimit1, + twgExitsPerFrameLimit1, + twgFrameDurationInSeconds1, + twgPrevExitRequestsLimit1, + twgCurrentRequestLimit2 + ) = contracts.triggerable_withdrawals_gateway.getExitRequestLimitFullInfo() (_,_,_, vebLimit1, vebLimit2) = contracts.validators_exit_bus_oracle.getExitRequestLimitFullInfo() assert vebLimit1 < vebInitLimit1 assert vebLimit2 < vebInitLimit2 - assert twgLimit1 < twgInitLimit1 - assert twgLimit2 < twgInitLimit2 + assert twgPrevExitRequestsLimit1 == twgCurrentRequestLimit2 + assert twgCurrentRequestLimit2 < twgInitCurrentRequestLimit2 assert len(tx.events["WithdrawalRequestAdded"]['request']) == 56 # 48 + 8 pubkey_bytes = tx.events["WithdrawalRequestAdded"]['request'][:48] _ = int.from_bytes(tx.events["WithdrawalRequestAdded"]['request'][48:], byteorder="big", signed=False) From 2c0115a4c301f245fc816c69a03ffc84524511b9 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 5 Dec 2025 18:22:46 +0000 Subject: [PATCH 106/178] fix: order for dup events --- .../node_operators_registry.py | 7 +- utils/tx_tracing.py | 73 ++++++++++++++++++- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/utils/test/event_validators/node_operators_registry.py b/utils/test/event_validators/node_operators_registry.py index 0fe3077bd..a586905b8 100644 --- a/utils/test/event_validators/node_operators_registry.py +++ b/utils/test/event_validators/node_operators_registry.py @@ -98,8 +98,11 @@ def validate_target_validators_count_changed_event(event: EventDict, t: TargetVa assert event.count("TargetValidatorsCountChanged") == 1 - assert event["TargetValidatorsCountChanged"]["nodeOperatorId"] == t.nodeOperatorId - assert event["TargetValidatorsCountChanged"]["targetValidatorsCount"] == t.targetValidatorsCount + #assert event["TargetValidatorsCountChanged"]["nodeOperatorId"] == t.nodeOperatorId + #assert event["TargetValidatorsCountChanged"]["targetValidatorsCount"] == t.targetValidatorsCount + assert event["TargetValidatorsCountChanged"]["ARG0_VALUE"] == t.nodeOperatorId + assert event["TargetValidatorsCountChanged"]["ARG1_VALUE"] == t.targetValidatorsCount + assert convert.to_address(event["TargetValidatorsCountChanged"]["_emitted_by"]) == convert.to_address( emitted_by ), "Wrong event emitter" diff --git a/utils/tx_tracing.py b/utils/tx_tracing.py index 06d465ced..568251c83 100644 --- a/utils/tx_tracing.py +++ b/utils/tx_tracing.py @@ -103,6 +103,72 @@ def validate_events_from_abis(): raise Exception(f"Event {topic} has different inputs in ABI") +def fix_duplicate_events(formatted_events, logs): + """ + Brownie's `_topics` object does not reliably handle events that share the same + signature across multiple interface files. When this happens, Brownie treats + them as indistinguishable: + + TargetValidatorsCountChanged event is populated over 4 contracts: + NodeOperatorsRegistry: + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode); + SDVT: + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode); + Sandbox: + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode); + CSModule: + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetLimitMode, uint256 targetValidatorsCount); + + Although these ABIs define the same event name and signature, Brownie collects + only one version of the event in its internal `_topics` map. Because ABI + parsing order is undefined and environment-dependent (verified experimentally), + the version that ends up in `_topics` may differ between runs. As a result, + the argument order inferred by Brownie for `TargetValidatorsCountChanged` + becomes inconsistent, breaking `eth_event.decode_logs` when decoding logs. + + To mitigate this, we explicitly append ordered parameters for each + encountered `TargetValidatorsCountChanged` event. This allows us to + validate arguments based on their positional order rather than relying + on potentially incorrect parameter names. + """ + + TARGET_TOPIC = web3.keccak(text='TargetValidatorsCountChanged(uint256,uint256,uint256)').hex().lower() + target_events = [] + + for i, log in enumerate(logs): + topics = log['topics'] or [] + if len(topics) == 0: + continue + if topics[0].lower() == TARGET_TOPIC: + target_events.append(i) + + for ev_index in target_events: + # 1st (indexed) uint256 argument value + formatted_events[ev_index]['data'].append({ + "name": "ARG0_VALUE", + "type": "uint256", + "value": int(logs[ev_index]['topics'][1], 16), + }) + + data_hex = logs[ev_index]['data'] + if data_hex.startswith("0x"): + data_hex = data_hex[2:] + + # 2nd (unindexed) uint256 argument value + formatted_events[ev_index]['data'].append({ + "name": "ARG1_VALUE", + "type": "uint256", + "value": int(data_hex[0:64], 16), + }) + + # 3rd (unindexed) uint256 argument value + formatted_events[ev_index]['data'].append({ + "name": "ARG2_VALUE", + "type": "uint256", + "value": int(data_hex[64:128], 16), + }) + + def tx_events_from_receipt(tx: TransactionReceipt) -> List: if not tx.status: raise "Tx has reverted status (set to 0)" @@ -122,7 +188,12 @@ def tx_events_from_receipt(tx: TransactionReceipt) -> List: log["data"] = data + "0" * missing_chars events = decode_logs(logs, _topics, allow_undecoded=True) - return [format_event(i) for i in events] + + formatted_events = [format_event(i) for i in events] + + fix_duplicate_events(formatted_events, logs) + + return formatted_events def tx_events_from_trace(tx: TransactionReceipt) -> Optional[List]: From 4bff6844dd0a32922b627867f6efc106095cbd2c Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 5 Dec 2025 19:55:27 +0100 Subject: [PATCH 107/178] Update Docker image version in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5067f6fad..7ffc5e892 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ _*may be optionally set when running tests asynchronously to reduce the risk of Run the container in the `scripts` directory and specify the ENV VARs: ```shell -docker run --name scripts -v "$(pwd)":/root/scripts -e ETH_RPC_URL -e ETH_RPC_URL2 -e ETH_RPC_URL3 -e PINATA_CLOUD_TOKEN -e DEPLOYER -e ETHERSCAN_TOKEN -e ETHERSCAN_TOKEN2 -e ETHERSCAN_TOKEN3 -d ghcr.io/lidofinance/scripts:v20 +docker run --name scripts -v "$(pwd)":/root/scripts -e ETH_RPC_URL -e ETH_RPC_URL2 -e ETH_RPC_URL3 -e PINATA_CLOUD_TOKEN -e DEPLOYER -e ETHERSCAN_TOKEN -e ETHERSCAN_TOKEN2 -e ETHERSCAN_TOKEN3 -d ghcr.io/lidofinance/scripts:v21 ``` #### Step 4. Initialize container From 001b256bd2211092beea5bc2de797aa83193e6fc Mon Sep 17 00:00:00 2001 From: Aleksandr Tarelkin Date: Fri, 5 Dec 2025 22:20:53 +0300 Subject: [PATCH 108/178] feat: dual governance regressions with lido v3 support --- .github/workflows/dual_governance_regression.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index c075be554..dc983768e 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -50,7 +50,7 @@ jobs: uses: actions/checkout@v4 with: repository: lidofinance/dual-governance - ref: feature/escrow-mainnet-deploy + ref: feature/report-simulation path: dual-governance persist-credentials: false token: ${{ secrets.GITHUB_TOKEN }} From 48fe540805de895a578df35d58afb7e9fd8c676e Mon Sep 17 00:00:00 2001 From: dry914 Date: Sat, 6 Dec 2025 00:09:50 +0300 Subject: [PATCH 109/178] test: update v3 vote test --- tests/utils_test_2025_12_10_lidov3.py | 198 ++++++++++++-------------- utils/test/event_validators/aragon.py | 96 +++++++++++-- 2 files changed, 181 insertions(+), 113 deletions(-) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index a4ea7dbf4..f957fcaa8 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -21,6 +21,7 @@ from utils.test.event_validators.common import validate_events_chain from utils.test.event_validators.proxy import validate_proxy_upgrade_event from utils.test.event_validators.permission import validate_grant_role_event, validate_revoke_role_event +from utils.test.event_validators.aragon import validate_aragon_set_app_event, validate_aragon_grant_permission_event, validate_aragon_revoke_permission_event # ============================================================================ @@ -48,17 +49,21 @@ EASYTRACK_EVMSCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" # Old Lido addresses -STETH = "0xAE7ab96520DE3A18E5e111B5EaAb095312D7fE84" LIDO_LOCATOR = "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" +HASH_CONSENSUS = "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288" # HashConsensus for AccountingOracle STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" ACL = "0x9895F0F17cc1d1891b6f18ee0b483B6f221b37Bb" -NODE_OPERATORS_REGISTRY = "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" -SIMPLE_DVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" ORACLE_DAEMON_CONFIG = "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09" CSM_ACCOUNTING = "0x4d72BFF1BeaC69925F8Bd12526a39BAAb069e5Da" OLD_BURNER = "0xD15a672319Cf0352560eE76d9e89eAB0889046D3" LIDO_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" +WITHDRAWAL_QUEUE = "0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1" + +# Our custom Aragon apps +LIDO = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" +NODE_OPERATORS_REGISTRY = "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" +SIMPLE_DVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" # New Lido V3 addresses VAULT_HUB = "0x1d201BE093d847f6446530Efb0E8Fb426d176709" @@ -76,6 +81,7 @@ RESEAL_MANAGER = "0x7914b5a1539b97Bd0bbd155757F25FD79A522d24" BURNER = "0xE76c52750019b80B43E36DF30bf4060EB73F573a" +# New Easy Track factories ALTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0xa29173C7BCf39dA48D5E404146A652d7464aee14" REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY = "0x194A46DA1947E98c9D79af13E06Cfbee0D8610cC" REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0x5292A1284e4695B95C0840CF8ea25A818751C17F" @@ -85,12 +91,16 @@ UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY = "0x8Bdc726a3147D8187820391D7c6F9F942606aEe6" UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY = "0x5C3bDFa3E7f312d8cf72F56F2b797b026f6B471c" +# New versions of apps after upgrade NEW_LIDO_VERSION = 3 NEW_ACCOUNTING_ORACLE_VERSION = 4 +NEW_HASH_CONSENSUS_VERSION = 5 UTC14 = 60 * 60 * 14 UTC23 = 60 * 60 * 23 SLASHING_RESERVE_SHIFT = 8192 +MAX_EXTERNAL_RATIO_BP = 300 # 3% +INFINITE_ALLOWANCE = 2**256 - 1 # type(uint256).max # ============================================================================ @@ -107,93 +117,6 @@ def get_ossifiable_proxy_impl(proxy_address): # =================== Aragon event validators for DG ========================= # ============================================================================ -def validate_aragon_grant_permission_event( - event, - entity: str, - app: str, - role: str, - emitted_by: str, -) -> None: - """ - Validate Aragon ACL SetPermission event for granting permission via DG proposal. - Ensures only expected events are fired and all parameters are correct. - """ - _events_chain = ["LogScriptCall", "SetPermission", "ScriptResult", "Executed"] - - validate_events_chain([e.name for e in event], _events_chain) - - assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" - assert event.count("SetPermission") == 1, f"Expected 1 SetPermission, got {event.count('SetPermission')}" - assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" - assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" - - assert event["SetPermission"]["allowed"] is True, "Permission should be granted (allowed=True)" - assert event["SetPermission"]["entity"] == entity, f"Wrong entity: expected {entity}, got {event['SetPermission']['entity']}" - assert event["SetPermission"]["app"] == app, f"Wrong app: expected {app}, got {event['SetPermission']['app']}" - assert event["SetPermission"]["role"] == role, f"Wrong role: expected {role}, got {event['SetPermission']['role']}" - - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), f"Wrong event emitter: expected {emitted_by}" - - -def validate_aragon_revoke_permission_event( - event, - entity: str, - app: str, - role: str, - emitted_by: str, -) -> None: - """ - Validate Aragon ACL SetPermission event for revoking permission via DG proposal. - Ensures only expected events are fired and all parameters are correct. - """ - _events_chain = ["LogScriptCall", "SetPermission", "ScriptResult", "Executed"] - - validate_events_chain([e.name for e in event], _events_chain) - - assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" - assert event.count("SetPermission") == 1, f"Expected 1 SetPermission, got {event.count('SetPermission')}" - assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" - assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" - - assert event["SetPermission"]["allowed"] is False, "Permission should be revoked (allowed=False)" - assert event["SetPermission"]["entity"] == entity, f"Wrong entity: expected {entity}, got {event['SetPermission']['entity']}" - assert event["SetPermission"]["app"] == app, f"Wrong app: expected {app}, got {event['SetPermission']['app']}" - assert event["SetPermission"]["role"] == role, f"Wrong role: expected {role}, got {event['SetPermission']['role']}" - - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), f"Wrong event emitter: expected {emitted_by}" - - -def validate_set_app_event( - event, - app_id: str, - app: str, - emitted_by: str, -) -> None: - """ - Validate Aragon Kernel SetApp event via DG proposal. - Ensures only expected events are fired and all parameters are correct. - """ - _events_chain = ["LogScriptCall", "SetApp", "ScriptResult", "Executed"] - - validate_events_chain([e.name for e in event], _events_chain) - - assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" - assert event.count("SetApp") == 1, f"Expected 1 SetApp, got {event.count('SetApp')}" - assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" - assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" - - assert event["SetApp"]["appId"] == app_id, f"Wrong appId: expected {app_id}, got {event['SetApp']['appId']}" - assert event["SetApp"]["app"] == app, f"Wrong app: expected {app}, got {event['SetApp']['app']}" - - assert convert.to_address(event["SetApp"]["_emitted_by"]) == convert.to_address( - emitted_by - ), f"Wrong event emitter: expected {emitted_by}" - - def validate_config_value_set_event( event, key: str, @@ -240,12 +163,12 @@ def validate_upgrade_started_event(events) -> None: ), f"Wrong event emitter: expected {UPGRADE_TEMPLATE}" -def validate_upgrade_finished_event(events) -> None: +def validate_upgrade_finished_events(events) -> None: """ - Validate V3Template UpgradeFinished event via DG proposal. + Validate V3Template UpgradeFinished events via DG proposal. Ensures only expected events are fired. """ - _events_chain = ["LogScriptCall", "ContractVersionSet", "Approval", "Approval", "Approval", "Approval", + _events_chain = ["LogScriptCall", "ContractVersionSet", "Transfer", "TransferShares", "Approval", "Approval", "Approval", "Approval", "MaxExternalRatioBPSet", "ContractVersionSet", "ConsensusVersionSet", "UpgradeFinished", "ScriptResult", "Executed"] validate_events_chain([e.name for e in events], _events_chain) @@ -259,6 +182,51 @@ def validate_upgrade_finished_event(events) -> None: assert events.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {events.count('ScriptResult')}" assert events.count("Executed") == 1, f"Expected 1 Executed, got {events.count('Executed')}" + lido_version = events["ContractVersionSet"][0]["version"] + assert lido_version == NEW_LIDO_VERSION, f"Wrong version: expected {NEW_LIDO_VERSION}, got {lido_version}" + assert convert.to_address(events["ContractVersionSet"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + # Transfer and TransferShares events are emitted only if old Burner has some shares on balance + if events.count("Transfer") > 0: + assert convert.to_address(events["Transfer"][0]["from"]) == OLD_BURNER, f"Wrong from: expected {OLD_BURNER}" + assert convert.to_address(events["Transfer"][0]["to"]) == BURNER, f"Wrong to: expected {BURNER}" + assert convert.to_address(events["Transfer"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + assert convert.to_address(events["TransferShares"][0]["from"]) == OLD_BURNER, f"Wrong from: expected {OLD_BURNER}" + assert convert.to_address(events["TransferShares"][0]["to"]) == BURNER, f"Wrong to: expected {BURNER}" + assert convert.to_address(events["TransferShares"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + assert convert.to_address(events["Approval"][0]["owner"]) == WITHDRAWAL_QUEUE, f"Wrong owner: expected {WITHDRAWAL_QUEUE}" + assert convert.to_address(events["Approval"][0]["spender"]) == OLD_BURNER, f"Wrong spender: expected {OLD_BURNER}" + assert convert.to_uint(events["Approval"][0]["value"]) == 0, f"Wrong value: expected {0}" + assert convert.to_address(events["Approval"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + assert convert.to_address(events["Approval"][1]["owner"]) == WITHDRAWAL_QUEUE, f"Wrong owner: expected {WITHDRAWAL_QUEUE}" + assert convert.to_address(events["Approval"][1]["spender"]) == BURNER, f"Wrong spender: expected {BURNER}" + assert convert.to_uint(events["Approval"][1]["value"]) == INFINITE_ALLOWANCE, f"Wrong value: expected {INFINITE_ALLOWANCE}" + assert convert.to_address(events["Approval"][1]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + assert convert.to_address(events["Approval"][2]["owner"]) == CSM_ACCOUNTING, f"Wrong owner: expected {CSM_ACCOUNTING}" + assert convert.to_address(events["Approval"][2]["spender"]) == OLD_BURNER, f"Wrong spender: expected {OLD_BURNER}" + assert convert.to_uint(events["Approval"][2]["value"]) == 0, f"Wrong value: expected {0}" + assert convert.to_address(events["Approval"][2]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + assert convert.to_address(events["Approval"][3]["owner"]) == CSM_ACCOUNTING, f"Wrong owner: expected {CSM_ACCOUNTING}" + assert convert.to_address(events["Approval"][3]["spender"]) == BURNER, f"Wrong spender: expected {BURNER}" + assert convert.to_uint(events["Approval"][3]["value"]) == INFINITE_ALLOWANCE, f"Wrong value: expected {INFINITE_ALLOWANCE}" + assert convert.to_address(events["Approval"][3]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + max_external_ratio_bp = events["MaxExternalRatioBPSet"][0]["maxExternalRatioBP"] + assert max_external_ratio_bp == MAX_EXTERNAL_RATIO_BP, f"Wrong max external ratio: expected {MAX_EXTERNAL_RATIO_BP}, got {max_external_ratio_bp}" + assert convert.to_address(events["MaxExternalRatioBPSet"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + oracle_version = events["ContractVersionSet"][1]["version"] + assert oracle_version == NEW_ACCOUNTING_ORACLE_VERSION, f"Wrong version: expected {NEW_ACCOUNTING_ORACLE_VERSION}, got {oracle_version}" + assert convert.to_address(events["ContractVersionSet"][1]["_emitted_by"]) == ACCOUNTING_ORACLE, f"Wrong event emitter: expected {ACCOUNTING_ORACLE}" + + consensus_version = events["ConsensusVersionSet"][0]["version"] + assert consensus_version == NEW_HASH_CONSENSUS_VERSION, f"Wrong version: expected {NEW_HASH_CONSENSUS_VERSION}, got {consensus_version}" + assert convert.to_address(events["ConsensusVersionSet"][0]["_emitted_by"]) == ACCOUNTING_ORACLE, f"Wrong event emitter: expected {ACCOUNTING_ORACLE}" + assert convert.to_address(events["UpgradeFinished"][0]["_emitted_by"]) == convert.to_address( UPGRADE_TEMPLATE ), f"Wrong event emitter: expected {UPGRADE_TEMPLATE}" @@ -337,7 +305,7 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): encode_oz_revoke_role( contract=old_burner, role_name="REQUEST_BURN_SHARES_ROLE", - revoke_from=STETH # Lido + revoke_from=LIDO ) ]), @@ -376,7 +344,7 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): encode_oz_revoke_role( contract=staking_router, role_name="REPORT_REWARDS_MINTED_ROLE", - revoke_from=STETH # Lido + revoke_from=LIDO ) ]), @@ -675,7 +643,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert not kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be different before upgrade" # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido - assert old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Lido before upgrade" + assert old_burner.hasRole(request_burn_shares_role, LIDO), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Lido before upgrade" # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module assert old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Curated staking module before upgrade" @@ -690,7 +658,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert accounting_oracle_impl_before != ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be different before upgrade" # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido - assert staking_router.hasRole(report_rewards_minted_role, STETH), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Lido before upgrade" + assert staking_router.hasRole(report_rewards_minted_role, LIDO), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Lido before upgrade" # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting assert not staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Accounting before upgrade" @@ -712,6 +680,18 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): except Exception: pass # Expected to fail + # Step 1.18. Call V3Template.finishUpgrade + lido = interface.Lido(LIDO) + assert lido.getContractVersion() == NEW_LIDO_VERSION - 1, "LIDO should have version 2 before finishUpgrade" + assert lido.allowance(CSM_ACCOUNTING, BURNER) == 0, "No allowance from CSM_ACCOUNTING to BURNER before finishUpgrade" + assert lido.allowance(CSM_ACCOUNTING, OLD_BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from CSM_ACCOUNTING to OLD_BURNER before finishUpgrade" + assert lido.allowance(WITHDRAWAL_QUEUE, BURNER) == 0, "No allowance from WITHDRAWAL_QUEUE to BURNER before finishUpgrade" + assert lido.allowance(WITHDRAWAL_QUEUE, OLD_BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from WITHDRAWAL_QUEUE to OLD_BURNER before finishUpgrade" + + accounting_oracle = interface.AccountingOracle(ACCOUNTING_ORACLE) + assert accounting_oracle.getContractVersion() == NEW_ACCOUNTING_ORACLE_VERSION - 1, "AccountingOracle should have version 3 before finishUpgrade" + assert accounting_oracle.getConsensusVersion() == NEW_HASH_CONSENSUS_VERSION - 1, "HashConsensus should have version 4 before finishUpgrade" + if details["status"] == PROPOSAL_STATUS["submitted"]: chain.sleep(timelock.getAfterSubmitDelay() + 1) dual_governance.scheduleProposal(expected_dg_proposal_id, {"from": stranger}) @@ -749,7 +729,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): ) # 1.5. Set Lido implementation in Kernel - validate_set_app_event( + validate_aragon_set_app_event( dg_events[4], app_id=LIDO_APP_ID, app=LIDO_IMPL, @@ -769,7 +749,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): validate_revoke_role_event( dg_events[6], role=request_burn_shares_role.hex(), - revoke_from=STETH, + revoke_from=LIDO, sender=AGENT, emitted_by=old_burner, ) @@ -808,7 +788,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): validate_revoke_role_event( dg_events[11], role=report_rewards_minted_role.hex(), - revoke_from=STETH, + revoke_from=LIDO, sender=AGENT, emitted_by=staking_router, ) @@ -857,7 +837,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): ) # 1.18. Call V3Template.finishUpgrade - validate_upgrade_finished_event(dg_events[17]) + validate_upgrade_finished_events(dg_events[17]) # ========================================================================= # ==================== After DG proposal executed checks ================== @@ -873,7 +853,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE after upgrade" # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido - assert not old_burner.hasRole(request_burn_shares_role, STETH), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Lido after upgrade" + assert not old_burner.hasRole(request_burn_shares_role, LIDO), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Lido after upgrade" # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module assert not old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Curated staking module after upgrade" @@ -888,7 +868,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert get_ossifiable_proxy_impl(accounting_oracle_proxy) == ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be updated to the new value" # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido - assert not staking_router.hasRole(report_rewards_minted_role, STETH), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Lido after upgrade" + assert not staking_router.hasRole(report_rewards_minted_role, LIDO), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Lido after upgrade" # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting assert staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Accounting after upgrade" @@ -902,3 +882,15 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # Step 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent after upgrade" + # Step 1.18. Call V3Template.finishUpgrade + lido = interface.Lido(LIDO) + assert lido.getContractVersion() == NEW_LIDO_VERSION, "LIDO should have version 3 after finishUpgrade" + assert lido.getMaxExternalRatioBP() == MAX_EXTERNAL_RATIO_BP, "LIDO should have max external ratio 3% after finishUpgrade" + assert lido.allowance(CSM_ACCOUNTING, OLD_BURNER) == 0, "No allowance from CSM_ACCOUNTING to OLD_BURNER after finishUpgrade" + assert lido.allowance(CSM_ACCOUNTING, BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from CSM_ACCOUNTING to BURNER after finishUpgrade" + assert lido.allowance(WITHDRAWAL_QUEUE, OLD_BURNER) == 0, "No allowance from WITHDRAWAL_QUEUE to OLD_BURNER after finishUpgrade" + assert lido.allowance(WITHDRAWAL_QUEUE, BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from WITHDRAWAL_QUEUE to BURNER after finishUpgrade" + + accounting_oracle = interface.AccountingOracle(ACCOUNTING_ORACLE) + assert accounting_oracle.getContractVersion() == NEW_ACCOUNTING_ORACLE_VERSION, "AccountingOracle should have version 4 after finishUpgrade" + assert accounting_oracle.getConsensusVersion() == NEW_HASH_CONSENSUS_VERSION, "HashConsensus should have version 5 after finishUpgrade" diff --git a/utils/test/event_validators/aragon.py b/utils/test/event_validators/aragon.py index 475cfcb08..54d09c53c 100644 --- a/utils/test/event_validators/aragon.py +++ b/utils/test/event_validators/aragon.py @@ -1,24 +1,100 @@ from typing import Tuple, Annotated from .common import validate_events_chain from brownie.network.event import EventDict +from brownie import convert -def validate_app_update_event(event: EventDict, app_id: str, app_address: str): - _ldo_events_chain = ["LogScriptCall", "SetApp"] +def validate_push_to_repo_event(event: EventDict, semantic_version: Annotated[Tuple[int, int, int], 3]): + _ldo_events_chain = ["LogScriptCall", "NewVersion"] validate_events_chain([e.name for e in event], _ldo_events_chain) - assert event.count("SetApp") == 1 + assert event.count("NewVersion") == 1 - assert event["SetApp"]["appId"] == app_id, "Wrong app id" - assert event["SetApp"]["app"] == app_address, "Wrong app address" + assert event["NewVersion"]["semanticVersion"] == semantic_version, "Wrong version" +def validate_aragon_grant_permission_event( + event, + entity: str, + app: str, + role: str, + emitted_by: str, +) -> None: + """ + Validate Aragon ACL SetPermission event for granting permission via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetPermission", "ScriptResult", "Executed"] -def validate_push_to_repo_event(event: EventDict, semantic_version: Annotated[Tuple[int, int, int], 3]): - _ldo_events_chain = ["LogScriptCall", "NewVersion"] + validate_events_chain([e.name for e in event], _events_chain) - validate_events_chain([e.name for e in event], _ldo_events_chain) + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetPermission") == 1, f"Expected 1 SetPermission, got {event.count('SetPermission')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" - assert event.count("NewVersion") == 1 + assert event["SetPermission"]["allowed"] is True, "Permission should be granted (allowed=True)" + assert event["SetPermission"]["entity"] == entity, f"Wrong entity: expected {entity}, got {event['SetPermission']['entity']}" + assert event["SetPermission"]["app"] == app, f"Wrong app: expected {app}, got {event['SetPermission']['app']}" + assert event["SetPermission"]["role"] == role, f"Wrong role: expected {role}, got {event['SetPermission']['role']}" - assert event["NewVersion"]["semanticVersion"] == semantic_version, "Wrong version" + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" + + +def validate_aragon_revoke_permission_event( + event, + entity: str, + app: str, + role: str, + emitted_by: str, +) -> None: + """ + Validate Aragon ACL SetPermission event for revoking permission via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetPermission", "ScriptResult", "Executed"] + + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetPermission") == 1, f"Expected 1 SetPermission, got {event.count('SetPermission')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" + + assert event["SetPermission"]["allowed"] is False, "Permission should be revoked (allowed=False)" + assert event["SetPermission"]["entity"] == entity, f"Wrong entity: expected {entity}, got {event['SetPermission']['entity']}" + assert event["SetPermission"]["app"] == app, f"Wrong app: expected {app}, got {event['SetPermission']['app']}" + assert event["SetPermission"]["role"] == role, f"Wrong role: expected {role}, got {event['SetPermission']['role']}" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" + + +def validate_aragon_set_app_event( + event, + app_id: str, + app: str, + emitted_by: str, +) -> None: + """ + Validate Aragon Kernel SetApp event via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetApp", "ScriptResult", "Executed"] + + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetApp") == 1, f"Expected 1 SetApp, got {event.count('SetApp')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" + + assert event["SetApp"]["appId"] == app_id, f"Wrong appId: expected {app_id}, got {event['SetApp']['appId']}" + assert event["SetApp"]["app"] == app, f"Wrong app: expected {app}, got {event['SetApp']['app']}" + + assert convert.to_address(event["SetApp"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" From 1785e342755563fad6ba081aa9d158a54df9d7c9 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 07:34:26 +0000 Subject: [PATCH 110/178] refactoring --- tests/utils_test_2025_12_10_operations.py | 3 ++- utils/test/event_validators/node_operators_registry.py | 6 ++++++ utils/tx_tracing.py | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py index d69860d41..873eaaa06 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_10_operations.py @@ -139,6 +139,7 @@ class TokenLimit(NamedTuple): A41_TARGET_CHANGE_REQUEST = TargetValidatorsCountChanged( nodeOperatorId=A41_NO_ID, targetValidatorsCount=NEW_A41_TARGET_LIMIT, + targetLimitMode=NO_TARGET_LIMIT_SOFT_MODE_AFTER, ) @@ -937,10 +938,10 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): a41_summary_after = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) assert a41_summary_after['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_AFTER assert a41_summary_after['depositableValidatorsCount'] == 0 + assert a41_summary_after['targetValidatorsCount'] == 0 assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" # additional checks to make sure no other fields were changed (if before state is available) if a41_summary_before is not None: - assert a41_summary_after['targetValidatorsCount'] == a41_summary_before['targetValidatorsCount'] assert a41_summary_after['stuckValidatorsCount'] == a41_summary_before['stuckValidatorsCount'] assert a41_summary_after['refundedValidatorsCount'] == a41_summary_before['refundedValidatorsCount'] assert a41_summary_after['stuckPenaltyEndTimestamp'] == a41_summary_before['stuckPenaltyEndTimestamp'] diff --git a/utils/test/event_validators/node_operators_registry.py b/utils/test/event_validators/node_operators_registry.py index a586905b8..7f76faa38 100644 --- a/utils/test/event_validators/node_operators_registry.py +++ b/utils/test/event_validators/node_operators_registry.py @@ -27,6 +27,7 @@ class NodeOperatorRewardAddressSetItem(NamedTuple): class TargetValidatorsCountChanged(NamedTuple): nodeOperatorId: int targetValidatorsCount: int + targetLimitMode: int def validate_node_operator_added_event( event: EventDict, node_operator_item: NodeOperatorItem @@ -100,8 +101,13 @@ def validate_target_validators_count_changed_event(event: EventDict, t: TargetVa #assert event["TargetValidatorsCountChanged"]["nodeOperatorId"] == t.nodeOperatorId #assert event["TargetValidatorsCountChanged"]["targetValidatorsCount"] == t.targetValidatorsCount + + # validate arguments based on their positional order + # NodeOperatorsRegistry + # event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode) assert event["TargetValidatorsCountChanged"]["ARG0_VALUE"] == t.nodeOperatorId assert event["TargetValidatorsCountChanged"]["ARG1_VALUE"] == t.targetValidatorsCount + assert event["TargetValidatorsCountChanged"]["ARG2_VALUE"] == t.targetLimitMode assert convert.to_address(event["TargetValidatorsCountChanged"]["_emitted_by"]) == convert.to_address( emitted_by diff --git a/utils/tx_tracing.py b/utils/tx_tracing.py index 568251c83..7dcca6e2d 100644 --- a/utils/tx_tracing.py +++ b/utils/tx_tracing.py @@ -140,7 +140,6 @@ def fix_duplicate_events(formatted_events, logs): if len(topics) == 0: continue if topics[0].lower() == TARGET_TOPIC: - target_events.append(i) for ev_index in target_events: # 1st (indexed) uint256 argument value From 5cad5251093e0e7907ad1921b7b0fbdc0d6cf21d Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 07:38:09 +0000 Subject: [PATCH 111/178] refactoring (ii) --- tests/utils_test_2025_12_10_operations.py | 14 +++++++------- utils/tx_tracing.py | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py index 873eaaa06..941cd1e2f 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_10_operations.py @@ -133,13 +133,13 @@ class TokenLimit(NamedTuple): ALLOWED_TOKENS_AFTER = 4 A41_NO_ID = 32 -NO_TARGET_LIMIT_SOFT_MODE_BEFORE = 0 -NO_TARGET_LIMIT_SOFT_MODE_AFTER = 1 +NO_TARGET_LIMIT_MODE_BEFORE = 0 +NO_TARGET_LIMIT_MODE_AFTER = 1 NEW_A41_TARGET_LIMIT = 0 A41_TARGET_CHANGE_REQUEST = TargetValidatorsCountChanged( nodeOperatorId=A41_NO_ID, targetValidatorsCount=NEW_A41_TARGET_LIMIT, - targetLimitMode=NO_TARGET_LIMIT_SOFT_MODE_AFTER, + targetLimitMode=NO_TARGET_LIMIT_MODE_AFTER, ) @@ -372,7 +372,7 @@ def dual_governance_proposal_calls(): agent_forward([ ( staking_router.address, - staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE_AFTER, NEW_A41_TARGET_LIMIT), + staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_MODE_AFTER, NEW_A41_TARGET_LIMIT), ) ]), agent_forward([ @@ -811,7 +811,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): # Item 1.3 a41_summary_before = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) - assert a41_summary_before['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_BEFORE + assert a41_summary_before['targetLimitMode'] == NO_TARGET_LIMIT_MODE_BEFORE assert a41_summary_before['depositableValidatorsCount'] > 0 assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" @@ -936,9 +936,9 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): # Item 1.3 a41_summary_after = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) - assert a41_summary_after['targetLimitMode'] == NO_TARGET_LIMIT_SOFT_MODE_AFTER + assert a41_summary_after['targetLimitMode'] == NO_TARGET_LIMIT_MODE_AFTER assert a41_summary_after['depositableValidatorsCount'] == 0 - assert a41_summary_after['targetValidatorsCount'] == 0 + assert a41_summary_after['targetValidatorsCount'] == NEW_A41_TARGET_LIMIT assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" # additional checks to make sure no other fields were changed (if before state is available) if a41_summary_before is not None: diff --git a/utils/tx_tracing.py b/utils/tx_tracing.py index 7dcca6e2d..568251c83 100644 --- a/utils/tx_tracing.py +++ b/utils/tx_tracing.py @@ -140,6 +140,7 @@ def fix_duplicate_events(formatted_events, logs): if len(topics) == 0: continue if topics[0].lower() == TARGET_TOPIC: + target_events.append(i) for ev_index in target_events: # 1st (indexed) uint256 argument value From 929157bd0acf708d074aacdfd26135bd0646f117 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 10:33:34 +0000 Subject: [PATCH 112/178] fix: ci timeout + descs --- .github/workflows/normal_vote_ci.yml | 4 ++-- scripts/vote_2025_12_10.py | 9 +++++++-- tests/utils_test_2025_12_10_operations.py | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/normal_vote_ci.yml b/.github/workflows/normal_vote_ci.yml index 8afa07907..087c4535d 100644 --- a/.github/workflows/normal_vote_ci.yml +++ b/.github/workflows/normal_vote_ci.yml @@ -13,7 +13,7 @@ jobs: run-tests-normal-1: name: Brownie fork NORMAL tests 1 runs-on: "ubuntu-latest" - timeout-minutes: 100 + timeout-minutes: 150 services: hardhat-node: @@ -37,7 +37,7 @@ jobs: run-tests-normal-2: name: Brownie fork NORMAL tests 2 runs-on: "ubuntu-latest" - timeout-minutes: 100 + timeout-minutes: 150 services: hardhat-node: diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py index f081abbe2..99b7354dc 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_10.py @@ -189,8 +189,13 @@ def amount_limits() -> List[Param]: # ============================= Description ================================== -# TODO -IPFS_DESCRIPTION = "omni nov 2025" +IPFS_DESCRIPTION = """ +1. **Change Curated Staking Module fee to 3.5%**, as per [Snapshot decision](https://snapshot.box/#/s:lido-snapshot.eth/proposal/0x3a5d10fcd3fad6d5ccf05f5bd49244046600ad9cbed9a5e07845200b3ae97e09). Item 1.1. +2. **Raise SDVT Staking Module stake share limit to 4.3% and priority exit threshold to 4.78%**, as [proposed on the Forum](https://research.lido.fi/t/staking-router-module-proposal-simple-dvt/5625/127). Item 1.2. +3. **Set A41 Node Operator soft target validators limit to 0**, as [requested on the Forum](https://research.lido.fi/t/a41-node-operator-intention-to-wind-down-operations-request-for-dao-vote/10954). Item 1.3. +4. **Set Easy Track TRP limit to 15'000'000 LDO**, as per [Snapshot decision](https://snapshot.box/#/s:lido-snapshot.eth/proposal/0x16ecb51631d67213d44629444fcc6275bc2abe4d7e955bebaf15c60a42cba471). Item 1.4. +5. **Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance**, as proposed in [TMC-6 on the Forum](https://research.lido.fi/t/tmc-6-convert-dao-treasury-stablecoins-into-susds-and-update-config-on-easy-track-and-aragon-finance-accordingly/10868). Items 2-6. +6. **Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig**, as proposed in [TMC-5 on the Forum](https://research.lido.fi/t/tmc-5-convert-matic-to-usdc/10814). Item 7.""" # ================================ Main ====================================== diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py index d69860d41..aa287c50e 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_10_operations.py @@ -406,7 +406,7 @@ def enact_and_test_voting( EXPECTED_DG_PROPOSAL_ID, ): EXPECTED_VOTE_EVENTS_COUNT = 7 - IPFS_DESCRIPTION_HASH = "bafkreigs2dewxxu7rj6eifpxsqvib23nsiw2ywsmh3lhewyqlmyn46obnm" + IPFS_DESCRIPTION_HASH = "bafkreieptki4mrkhpd22ij3cym777l4iivrdknwtglchdtvptujz2dgn7u" # ======================================================================= # ========================= Arrange variables =========================== From 1f3895fe247af077655a39a0141415fe3abd4b08 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 10:41:20 +0000 Subject: [PATCH 113/178] fix: README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ffc5e892..994cee693 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ _*may be optionally set when running tests asynchronously to reduce the risk of Run the container in the `scripts` directory and specify the ENV VARs: ```shell -docker run --name scripts -v "$(pwd)":/root/scripts -e ETH_RPC_URL -e ETH_RPC_URL2 -e ETH_RPC_URL3 -e PINATA_CLOUD_TOKEN -e DEPLOYER -e ETHERSCAN_TOKEN -e ETHERSCAN_TOKEN2 -e ETHERSCAN_TOKEN3 -d ghcr.io/lidofinance/scripts:v21 +docker run --name scripts -v "$(pwd)":/root/scripts -e ETH_RPC_URL -e ETH_RPC_URL2 -e PINATA_CLOUD_TOKEN -e DEPLOYER -e ETHERSCAN_TOKEN -e ETHERSCAN_TOKEN2 -p 8545:8545 -d ghcr.io/lidofinance/scripts:v21 ``` #### Step 4. Initialize container From 594f187c6a76c94aad59f9005f514d5f1b875ebb Mon Sep 17 00:00:00 2001 From: dry914 Date: Sat, 6 Dec 2025 14:16:07 +0300 Subject: [PATCH 114/178] test: add new Lido slots snap tests --- configs/config_mainnet.py | 2 + tests/snapshot/test_lido_snapshot.py | 184 ++++++++++++++++++++++++++- 2 files changed, 185 insertions(+), 1 deletion(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 61846cbf7..aa513ccca 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -540,3 +540,5 @@ VAULTS_ADAPTER = "0xe2DE6d2DefF15588a71849c0429101F8ca9FB14D" GATE_SEAL_V3 = "0x881dAd714679A6FeaA636446A0499101375A365c" + +INITIAL_MAX_EXTERNAL_RATIO_BP = 300 # 3% diff --git a/tests/snapshot/test_lido_snapshot.py b/tests/snapshot/test_lido_snapshot.py index da89f9f15..38afe479f 100644 --- a/tests/snapshot/test_lido_snapshot.py +++ b/tests/snapshot/test_lido_snapshot.py @@ -10,7 +10,7 @@ from web3.types import Wei from tests.conftest import Helpers -from utils.config import contracts, LDO_TOKEN, VOTING, AGENT +from utils.config import contracts, LDO_TOKEN, VOTING, AGENT, INITIAL_MAX_EXTERNAL_RATIO_BP from utils.evm_script import EMPTY_CALLSCRIPT from utils.test.governance_helpers import execute_vote_and_process_dg_proposals from utils.test.snapshot_helpers import _chain_snapshot @@ -42,6 +42,7 @@ class Frame(TypedDict): "lido.Lido.beaconValidators": ZERO_BYTES32, "lido.Lido.bufferedEther": ZERO_BYTES32, "lido.Lido.depositedValidators": ZERO_BYTES32, + "lido.Lido.lidoLocator": ZERO_BYTES32, "lido.StETH.totalShares": ZERO_BYTES32, } @@ -338,8 +339,11 @@ def _snap(): # Lido.sol "lido.Lido.beaconBalance", "lido.Lido.beaconValidators", + "lido.Lido.clBalanceAndClValidators", "lido.Lido.bufferedEther", + "lido.Lido.bufferedEtherAndDepositedValidators", "lido.Lido.depositContract", + "lido.Lido.lidoLocator", "lido.Lido.depositedValidators", "lido.Lido.ELRewardsWithdrawalLimit", "lido.Lido.executionLayerRewardsVault", @@ -354,8 +358,10 @@ def _snap(): "lido.Lido.treasury", "lido.Lido.treasuryFee", "lido.Lido.withdrawalCredentials", + "lido.Lido.lidoLocatorAndMaxExternalRatio", # StETH.sol "lido.StETH.totalShares", + "lido.StETH.totalAndExternalShares", # Pausable.sol "lido.Pausable.activeFlag", # AragonApp.sol @@ -479,6 +485,50 @@ def _stacks_equal(stacks: tuple[Stack, Stack]) -> None: with check: unexpected: dict[str, tuple[Any, Any]] = {} for key, before_val in v1_frame["snap"].items(): + if key == "lido.Lido.bufferedEtherAndDepositedValidators": + if not _combined_buffered_and_deposited_slot_ok( + v1_frame["snap"], + v2_frame["snap"], + ): + unexpected[key] = ( + v1_frame["snap"]["lido.Lido.bufferedEtherAndDepositedValidators"], + v2_frame["snap"]["lido.Lido.bufferedEtherAndDepositedValidators"], + ) + continue + + if key == "lido.Lido.clBalanceAndClValidators": + if not _combined_cl_balance_and_validators_slot_ok( + v1_frame["snap"], + v2_frame["snap"], + ): + unexpected[key] = ( + v1_frame["snap"]["lido.Lido.clBalanceAndClValidators"], + v2_frame["snap"]["lido.Lido.clBalanceAndClValidators"], + ) + continue + + if key == "lido.StETH.totalAndExternalShares": + if not _combined_steth_total_and_external_shares_slot_ok( + v1_frame["snap"], + v2_frame["snap"], + ): + unexpected[key] = ( + v1_frame["snap"]["lido.StETH.totalAndExternalShares"], + v2_frame["snap"]["lido.StETH.totalAndExternalShares"], + ) + continue + + if key == "lido.Lido.lidoLocatorAndMaxExternalRatio": + if not _combined_lido_locator_and_max_external_ratio_slot_ok( + v1_frame["snap"], + v2_frame["snap"], + ): + unexpected[key] = ( + v1_frame["snap"]["lido.Lido.lidoLocatorAndMaxExternalRatio"], + v2_frame["snap"]["lido.Lido.lidoLocatorAndMaxExternalRatio"], + ) + continue + if key in IGNORED_SNAPSHOT_KEYS: continue after_val = v2_frame["snap"].get(key) @@ -491,3 +541,135 @@ def _stacks_equal(stacks: tuple[Stack, Stack]) -> None: assert not unexpected, ( f"Snapshots after {v1_frame['func']} differ unexpectedly: {unexpected}" ) + + +def _combined_buffered_and_deposited_slot_ok( + v1_snap: dict[str, Any], + v2_snap: dict[str, Any], +) -> bool: + """ + Since v3, buffered ether and deposited validators are packed into one slot: + |------ 128 bit -------|------ 128 bit -------| + | deposited validators | buffered ether | + """ + before_combined = v1_snap["lido.Lido.bufferedEtherAndDepositedValidators"] + after_combined = v2_snap["lido.Lido.bufferedEtherAndDepositedValidators"] + + # Before upgrade the new slot must be empty + if before_combined != ZERO_BYTES32: + return False + + # After upgrade the new slot packs high 128 bits as deposited validators + # and low 128 bits as buffered ether from the old slots. + old_buffered = int.from_bytes( + v1_snap["lido.Lido.bufferedEther"], + byteorder="big", + ) + old_deposited = int.from_bytes( + v1_snap["lido.Lido.depositedValidators"], + byteorder="big", + ) + combined_after = int.from_bytes(after_combined, byteorder="big") + low_mask = (1 << 128) - 1 + buffered_after = combined_after & low_mask + deposited_after = combined_after >> 128 + + return buffered_after == old_buffered and deposited_after == old_deposited + + +def _combined_cl_balance_and_validators_slot_ok( + v1_snap: dict[str, Any], + v2_snap: dict[str, Any], +) -> bool: + """ + Since v3, CL balance and CL validators are packed into one slot: + |----- 128 bit -----|------ 128 bit -------| + | CL validators | CL balance | + """ + before_combined = v1_snap["lido.Lido.clBalanceAndClValidators"] + after_combined = v2_snap["lido.Lido.clBalanceAndClValidators"] + + # Before upgrade the new slot must be empty + if before_combined != ZERO_BYTES32: + return False + + # After upgrade the new slot packs high 128 bits as validators count + # and low 128 bits as CL balance from the old slots. + old_balance = int.from_bytes( + v1_snap["lido.Lido.beaconBalance"], + byteorder="big", + ) + old_validators = int.from_bytes( + v1_snap["lido.Lido.beaconValidators"], + byteorder="big", + ) + combined_after = int.from_bytes(after_combined, byteorder="big") + low_mask = (1 << 128) - 1 + balance_after = combined_after & low_mask + validators_after = combined_after >> 128 + + return balance_after == old_balance and validators_after == old_validators + + +def _combined_steth_total_and_external_shares_slot_ok( + v1_snap: dict[str, Any], + v2_snap: dict[str, Any], +) -> bool: + """ + Since v3, total shares and external shares are packed into one slot: + |------ 128 bit -------|------ 128 bit -------| + | external shares | total shares | + """ + before_combined = v1_snap["lido.StETH.totalAndExternalShares"] + after_combined = v2_snap["lido.StETH.totalAndExternalShares"] + + # Before upgrade the new slot must be empty + if before_combined != ZERO_BYTES32: + return False + + combined_after = int.from_bytes(after_combined, byteorder="big") + low_mask = (1 << 128) - 1 + total_after_slot = combined_after & low_mask + external_after = combined_after >> 128 + + # External shares must be zero after upgrade + if external_after != 0: + return False + + # Total shares (low 128 bits) must equal the old storage slot value + total_shares_before_slot = int.from_bytes( + v1_snap["lido.StETH.totalShares"], + byteorder="big", + ) + return total_after_slot == total_shares_before_slot + + +def _combined_lido_locator_and_max_external_ratio_slot_ok( + v1_snap: dict[str, Any], + v2_snap: dict[str, Any], +) -> bool: + """ + Since v3, max external ratio and lido locator are packed into one slot: + |----- 96 bit -----|------ 160 bit -------| + |max external ratio| lido locator address | + """ + before_combined = v1_snap["lido.Lido.lidoLocatorAndMaxExternalRatio"] + after_combined = v2_snap["lido.Lido.lidoLocatorAndMaxExternalRatio"] + + # Before upgrade the new slot must be empty + if before_combined != ZERO_BYTES32: + return False + + combined_after = int.from_bytes(after_combined, byteorder="big") + + # Low 160 bits is locator address, high 96 bits is max external ratio BP + locator_mask = (1 << 160) - 1 + locator_after = combined_after & locator_mask + max_external_ratio_after = combined_after >> 160 + + if max_external_ratio_after != INITIAL_MAX_EXTERNAL_RATIO_BP: + return False + + # Locator address in the combined slot must match the old locator slot value + before_locator_int = int.from_bytes(v1_snap["lido.Lido.lidoLocator"], byteorder="big") + return locator_after == before_locator_int From db673a64713401072e9e7574afd794eda61c1839 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 11:16:15 +0000 Subject: [PATCH 115/178] fix: descs --- scripts/upgrade_2025_12_10_mainnet_v3.py | 52 ++++++++++++------------ tests/utils_test_2025_12_10_lidov3.py | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index de356336d..8b86726b9 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -2,34 +2,34 @@ # Vote 2025_12_10 === 1. DG PROPOSAL === -1.1. Ensure DG proposal execution is within daily time window (14:00 UTC - 23:00 UTC) -1.2. Call V3Template.startUpgrade +1.1. Check execution time window (14:00–23:00 UTC) +1.2. Call V3Template.startUpgrade() 1.3. Upgrade LidoLocator implementation -1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT -1.5. Set Lido implementation in Kernel -1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT -1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido -1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module -1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT -1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting +1.4. Grant APP_MANAGER_ROLE to Agent +1.5. Set Lido implementation in Aragon Kernel +1.6. Revoke APP_MANAGER_ROLE from Agent +1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido on old Burner +1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated Module on old Burner +1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT on old Burner +1.10. Revoke REQUEST_BURN_SHARES_ROLE from CSM Accounting on old Burner 1.11. Upgrade AccountingOracle implementation -1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido -1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting -1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent -1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT at OracleDaemonConfig -1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT at OracleDaemonConfig -1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent -1.18. Call V3Template.finishUpgrade +1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido on StakingRouter +1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting on StakingRouter +1.14. Grant CONFIG_MANAGER_ROLE to Agent on OracleDaemonConfig +1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT in OracleDaemonConfig +1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT in OracleDaemonConfig +1.17. Revoke CONFIG_MANAGER_ROLE from Agent on OracleDaemonConfig +1.18. Call V3Template.finishUpgrade() === NON-DG ITEMS === -2. Add AlterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid, alterTiers) -3. Add RegisterGroupsInOperatorGrid factory to Easy Track (permissions: operatorGrid, registerGroup + registerTiers) -4. Add RegisterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid, registerTiers) -5. Add UpdateGroupsShareLimitInOperatorGrid factory to Easy Track (permissions: operatorGrid, updateGroupShareLimit) -6. Add SetJailStatusInOperatorGrid factory to Easy Track (permissions: vaultsAdapter, setVaultJailStatus) -7. Add UpdateVaultsFeesInOperatorGrid factory to Easy Track (permissions: vaultsAdapter, updateVaultFees) -8. Add ForceValidatorExitsInVaultHub factory to Easy Track (permissions: vaultsAdapter, forceValidatorExit) -9. Add SocializeBadDebtInVaultHub factory to Easy Track (permissions: vaultsAdapter, socializeBadDebt) +2. Add AlterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid.alterTiers) +3. Add RegisterGroupsInOperatorGrid factory to Easy Track (permissions: operatorGrid.registerGroup, operatorGrid.registerTiers) +4. Add RegisterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid.registerTiers) +5. Add UpdateGroupsShareLimitInOperatorGrid factory to Easy Track (permissions: operatorGrid.updateGroupShareLimit) +6. Add SetJailStatusInOperatorGrid factory to Easy Track (permissions: vaultsAdapter.setVaultJailStatus) +7. Add UpdateVaultsFeesInOperatorGrid factory to Easy Track (permissions: vaultsAdapter.updateVaultFees) +8. Add ForceValidatorExitsInVaultHub factory to Easy Track (permissions: vaultsAdapter.forceValidatorExit) +9. Add SocializeBadDebtInVaultHub factory to Easy Track (permissions: vaultsAdapter.socializeBadDebt) # TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. """ @@ -51,8 +51,8 @@ # ============================= Description ================================== # TODO IPFS_DESCRIPTION = "omni dec 2025" -DG_PROPOSAL_DESCRIPTION = "TODO DG proposal description" -DG_SUBMISSION_DESCRIPTION = "1. TODO DG submission description" +DG_PROPOSAL_DESCRIPTION = "Activate Lido V3" +DG_SUBMISSION_DESCRIPTION = "1. Submit a Dual Governance proposal to activate Lido V3" # ================================ Main ====================================== diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index f957fcaa8..7243faaa7 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -514,7 +514,7 @@ def enact_and_test_voting( proposal_id=expected_dg_proposal_id, proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="TODO DG proposal description", + metadata="Activate Lido V3", proposal_calls=dual_governance_proposal_calls(), ) From 4b0f8f7cd15d8fb294b0d2fd0f2643aaf4482403 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Sat, 6 Dec 2025 12:07:54 +0000 Subject: [PATCH 116/178] feat: restore FORKING_BLOCK_NUMBER var --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 50a39055c..46b16e23d 100644 --- a/Makefile +++ b/Makefile @@ -86,11 +86,12 @@ node3: npx hardhat node --fork $(ETH_RPC_URL3) --port $(NODE_PORT) test-core: - FORKING_BLOCK_NUMBER=$$($(MAKE) --no-print-directory __get_rpc_latest_block_number) && \ - echo "FORKING_BLOCK_NUMBER: $$FORKING_BLOCK_NUMBER" && \ + LATEST_BLOCK_NUMBER=$$($(MAKE) --no-print-directory __get_rpc_latest_block_number) && \ + echo "LATEST_BLOCK_NUMBER: $$LATEST_BLOCK_NUMBER" && \ cd $(CORE_DIR) && \ RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ + FORKING_BLOCK_NUMBER=$$LATEST_BLOCK_NUMBER \ yarn test:integration slots: From 0f76bea156f46025f02e229ae266398da7445c9f Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Sat, 6 Dec 2025 13:16:19 +0000 Subject: [PATCH 117/178] chore: debug VaultNotFactoryDeployed error --- .github/workflows/core_tests.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 702bf1564..8303d5d24 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -36,7 +36,7 @@ jobs: - name: Run init script run: docker exec -e CORE_BRANCH tests-runner bash -c 'make init' env: - CORE_BRANCH: develop + CORE_BRANCH: chore/test-ci - name: Run node run: docker exec -e ETH_RPC_URL --detach tests-runner bash -c 'NODE_PORT=8546 make node' diff --git a/Makefile b/Makefile index 46b16e23d..f63c07e40 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ test-core: RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ FORKING_BLOCK_NUMBER=$$LATEST_BLOCK_NUMBER \ - yarn test:integration + yarn test:integration:trace slots: @echo "Input https://github.com/lidofinance/protocol-onchain-mon-bots/blob/main/bots/ethereum-steth-v2/src/utils/constants.ts file content (end with Enter and Ctrl+D):" From bc0d79af0d3effaf27e94d87e721fef40536114a Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 13:26:52 +0000 Subject: [PATCH 118/178] fix: lego ldo spendable balance value --- tests/utils_test_2025_12_10_operations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py index 8a66b82d3..199ed4442 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_10_operations.py @@ -26,7 +26,6 @@ ) from utils.test.event_validators.allowed_recipients_registry import ( validate_set_limit_parameter_event, - validate_set_spent_amount_event, ) from utils.test.event_validators.permission import ( validate_grant_role_event, @@ -76,7 +75,6 @@ class TokenLimit(NamedTuple): LEGO_LDO_TRUSTED_CALLER = "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030" LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x00caAeF11EC545B192f16313F53912E453c91458" LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY = "0x97615f72c3428A393d65A84A3ea6BBD9ad6C0D74" -LEGO_LDO_SPENDABLE_BALANCE = 1_000_000 * 10**18 GAS_SUPPLY_STETH_TRUSTED_CALLER = "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53" GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x200dA0b6a9905A377CF8D469664C65dB267009d1" @@ -680,6 +678,7 @@ def enact_and_test_voting( # check ET limits via Easy Track motion ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 + LEGO_LDO_SPENDABLE_BALANCE = interface.AllowedRecipientRegistry(LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) From e2a76e666419b60331228b93d78cd440b5e24464 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Sat, 6 Dec 2025 13:48:04 +0000 Subject: [PATCH 119/178] chore: switch back to develop --- .github/workflows/core_tests.yml | 3 ++- Makefile | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 8303d5d24..0e7a5d39f 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -9,6 +9,7 @@ on: - "feat/rc2" - "feat/rc1" - "feat/next-vote" + - "feat/v3-vote" - "feat/v3-vote-ci" schedule: - cron: "0 0 * * TUE" @@ -36,7 +37,7 @@ jobs: - name: Run init script run: docker exec -e CORE_BRANCH tests-runner bash -c 'make init' env: - CORE_BRANCH: chore/test-ci + CORE_BRANCH: develop - name: Run node run: docker exec -e ETH_RPC_URL --detach tests-runner bash -c 'NODE_PORT=8546 make node' diff --git a/Makefile b/Makefile index f63c07e40..46b16e23d 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ test-core: RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ FORKING_BLOCK_NUMBER=$$LATEST_BLOCK_NUMBER \ - yarn test:integration:trace + yarn test:integration slots: @echo "Input https://github.com/lidofinance/protocol-onchain-mon-bots/blob/main/bots/ethereum-steth-v2/src/utils/constants.ts file content (end with Enter and Ctrl+D):" From 1209698db1888e855e9d49c2e3a0410570390c77 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 14:06:17 +0000 Subject: [PATCH 120/178] fix for 2 scripts for core repo tests --- scripts/ci/prepare_environment.py | 7 ++++++- utils/test/governance_helpers.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/ci/prepare_environment.py b/scripts/ci/prepare_environment.py index 0c89dc6ea..3c72a3cea 100644 --- a/scripts/ci/prepare_environment.py +++ b/scripts/ci/prepare_environment.py @@ -2,16 +2,21 @@ from typing import Callable, Tuple, List +from tests.conftest import Helpers + from utils.config import contracts, get_deployer_account from utils.voting import bake_vote_items from utils.dual_governance import process_pending_proposals from utils.evm_script import encode_call_script from utils.import_current_votes import get_vote_script_files, get_upgrade_script_files from utils.mainnet_fork import pass_and_exec_dao_vote +from utils.test.governance_helpers import execute_vote_and_process_dg_proposals def main(): process_pending_proposals() - execute_votings_and_process_created_proposals() + # TODO revert after December Aragon + #execute_votings_and_process_created_proposals() + execute_vote_and_process_dg_proposals(Helpers, -1, -1) def execute_votings_and_process_created_proposals(): votings_in_flight = retrieve_votings_in_flight() diff --git a/utils/test/governance_helpers.py b/utils/test/governance_helpers.py index 76ea20a7c..da4061c6d 100644 --- a/utils/test/governance_helpers.py +++ b/utils/test/governance_helpers.py @@ -10,7 +10,7 @@ def execute_vote(helpers, vote_ids_from_env): else: start_and_execute_votes(contracts.voting, helpers) - +# TODO revert after December Aragon #def execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env): # if vote_ids_from_env and dg_proposal_ids_from_env: # execute_vote(helpers, vote_ids_from_env) From 48f80d6867f2245b1823092a2f9b5e90fac01399 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 14:30:16 +0000 Subject: [PATCH 121/178] fix: add ETHERSCAN_TOKEN to core tests run --- .github/workflows/core_tests.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 0e7a5d39f..f983c9b4b 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -22,7 +22,7 @@ jobs: services: tests-runner: - image: ghcr.io/lidofinance/scripts:v20 + image: ghcr.io/lidofinance/scripts:v21 ports: - 8546:8546 volumes: @@ -49,9 +49,10 @@ jobs: run: timeout 30 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' 127.0.0.1 8546 - name: Prepare test environment with Brownie - run: docker exec -e GITHUB_TOKEN -e ETH_RPC_URL tests-runner bash -c 'make ci-prepare-environment' + run: docker exec -e GITHUB_TOKEN -e ETH_RPC_URL -e ETHERSCAN_TOKEN tests-runner bash -c 'make ci-prepare-environment' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ETHERSCAN_TOKEN: ${{ secrets.ETHERSCAN_TOKEN }} - name: Run core tests run: docker exec -e CORE_TESTS_TARGET_RPC_URL tests-runner bash -c 'make test-core' From 2fcae2f3c73ca19fc8a298ae66870959dd13a3ed Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 14:34:30 +0000 Subject: [PATCH 122/178] fix: add mfh-1 network for dg tests --- .github/workflows/dual_governance_regression.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index dc983768e..e7bf8a6b4 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -142,7 +142,7 @@ jobs: working-directory: scripts - name: Prepare test environment with Brownie - run: poetry run brownie run scripts/ci/prepare_environment --network mainnet-fork + run: poetry run brownie run scripts/ci/prepare_environment --network mfh-1 working-directory: scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From a4fee03471a78ee07a5f5687a44bd1e2ec52b8b9 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 14:41:52 +0000 Subject: [PATCH 123/178] brownie network import for dg tests --- .github/workflows/dual_governance_regression.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index e7bf8a6b4..63a42f02b 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -141,6 +141,10 @@ jobs: done working-directory: scripts + - name: Import network config for brownie + shell: bash + run: poetry run brownie networks import network-config.yaml True + - name: Prepare test environment with Brownie run: poetry run brownie run scripts/ci/prepare_environment --network mfh-1 working-directory: scripts From c405d9cd5ce2fc61d3a607638b53f754750df7c0 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 14:43:57 +0000 Subject: [PATCH 124/178] brownie network import for dg tests --- .github/workflows/dual_governance_regression.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index 63a42f02b..7da7cf7d1 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -144,6 +144,7 @@ jobs: - name: Import network config for brownie shell: bash run: poetry run brownie networks import network-config.yaml True + working-directory: scripts - name: Prepare test environment with Brownie run: poetry run brownie run scripts/ci/prepare_environment --network mfh-1 From c729abf642bdc8672ac4a826f3b91350b94e2c60 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 14:50:27 +0000 Subject: [PATCH 125/178] etherscan token for dg tests --- .github/workflows/dual_governance_regression.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index 7da7cf7d1..2f90d4644 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -151,6 +151,7 @@ jobs: working-directory: scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ETHERSCAN_TOKEN: ${{ secrets.ETHERSCAN_TOKEN }} - name: Run regression tests run: npm run test:regressions -- --load-accounts From 617bf72bcd563831788d211fcd443046184a68c8 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 15:02:25 +0000 Subject: [PATCH 126/178] fix wait loop in core tests --- .github/workflows/core_tests.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index f983c9b4b..1bedff353 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -46,7 +46,18 @@ jobs: - name: Check that the fork is ready shell: bash - run: timeout 30 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' 127.0.0.1 8546 + run: | + echo "Waiting for fork node on 127.0.0.1:8546..." + for i in {1..30}; do + if (echo > /dev/tcp/127.0.0.1/8546) >/dev/null 2>&1; then + echo "Fork is ready ✅" + exit 0 + fi + echo "Not ready yet... ($i/30)" + sleep 1 + done + echo "❌ Fork was not ready after 30 seconds" >&2 + exit 1 - name: Prepare test environment with Brownie run: docker exec -e GITHUB_TOKEN -e ETH_RPC_URL -e ETHERSCAN_TOKEN tests-runner bash -c 'make ci-prepare-environment' From 53bd54f7fa963c0279ddc116597db206672b44b4 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 15:48:17 +0000 Subject: [PATCH 127/178] logs --- tests/conftest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 4a2dc83bc..913ee0da3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -129,10 +129,14 @@ def execute_votes(accounts, vote_ids, dao_voting, topup="10 ether"): dao_voting.vote(vote_id, True, False, {"from": account}) # wait for the vote to end + print("HERE1", dao_voting.getVote(vote_id)["startDate"], get_vote_duration(), chain.time()) time_to_end = dao_voting.getVote(vote_id)["startDate"] + get_vote_duration() - chain.time() + print("HERE2", time_to_end) if time_to_end > 0: + print("HERE3", "sleep") chain.sleep(time_to_end) chain.mine() + print("HERE4", "mine", vote_ids) for vote_id in vote_ids: assert dao_voting.canExecute(vote_id) From 0dc47ce3b8aff0849ad0d0410ac01bc99753a3e5 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 16:10:51 +0000 Subject: [PATCH 128/178] change network for core tests --- .github/workflows/core_tests.yml | 10 +++++----- Makefile | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 1bedff353..5de100613 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -24,7 +24,7 @@ jobs: tests-runner: image: ghcr.io/lidofinance/scripts:v21 ports: - - 8546:8546 + - 8545:8545 volumes: - ${{ github.workspace }}:/root/scripts options: >- @@ -40,16 +40,16 @@ jobs: CORE_BRANCH: develop - name: Run node - run: docker exec -e ETH_RPC_URL --detach tests-runner bash -c 'NODE_PORT=8546 make node' + run: docker exec -e ETH_RPC_URL --detach tests-runner bash -c 'NODE_PORT=8545 make node' env: ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} - name: Check that the fork is ready shell: bash run: | - echo "Waiting for fork node on 127.0.0.1:8546..." + echo "Waiting for fork node on 127.0.0.1:8545..." for i in {1..30}; do - if (echo > /dev/tcp/127.0.0.1/8546) >/dev/null 2>&1; then + if (echo > /dev/tcp/127.0.0.1/8545) >/dev/null 2>&1; then echo "Fork is ready ✅" exit 0 fi @@ -68,4 +68,4 @@ jobs: - name: Run core tests run: docker exec -e CORE_TESTS_TARGET_RPC_URL tests-runner bash -c 'make test-core' env: - CORE_TESTS_TARGET_RPC_URL: http://127.0.0.1:8546 + CORE_TESTS_TARGET_RPC_URL: http://127.0.0.1:8545 diff --git a/Makefile b/Makefile index 46b16e23d..b10dbef61 100644 --- a/Makefile +++ b/Makefile @@ -103,7 +103,7 @@ slots: @rm -f slots.ts ci-prepare-environment: - poetry run brownie run scripts/ci/prepare_environment --network $(SECONDARY_NETWORK) + poetry run brownie run scripts/ci/prepare_environment --network mfh-1 enact-fork: poetry run brownie run $(vote) start_and_execute_vote_on_fork_manual --network=mfh-1 From 041586baa60c0ace5288549d678ef8fef3f43a28 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 16:29:35 +0000 Subject: [PATCH 129/178] fix: vote time sleep --- tests/conftest.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 913ee0da3..f14b9cb04 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -129,14 +129,9 @@ def execute_votes(accounts, vote_ids, dao_voting, topup="10 ether"): dao_voting.vote(vote_id, True, False, {"from": account}) # wait for the vote to end - print("HERE1", dao_voting.getVote(vote_id)["startDate"], get_vote_duration(), chain.time()) - time_to_end = dao_voting.getVote(vote_id)["startDate"] + get_vote_duration() - chain.time() - print("HERE2", time_to_end) - if time_to_end > 0: - print("HERE3", "sleep") - chain.sleep(time_to_end) + # time_to_end = dao_voting.getVote(vote_id)["startDate"] + get_vote_duration() - chain.time() + chain.sleep(get_vote_duration()) chain.mine() - print("HERE4", "mine", vote_ids) for vote_id in vote_ids: assert dao_voting.canExecute(vote_id) From 5611fcf887fb3a42c2c1b29fde33b1a77fc5ddd6 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 16:37:39 +0000 Subject: [PATCH 130/178] sleep for fork in core tests --- .github/workflows/core_tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 5de100613..3e6a43c90 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -48,6 +48,7 @@ jobs: shell: bash run: | echo "Waiting for fork node on 127.0.0.1:8545..." + sleep 10 for i in {1..30}; do if (echo > /dev/tcp/127.0.0.1/8545) >/dev/null 2>&1; then echo "Fork is ready ✅" From 88bf6927acb69675df392f33dfd20cd1dc0ce5f7 Mon Sep 17 00:00:00 2001 From: dry914 Date: Sat, 6 Dec 2025 20:45:56 +0300 Subject: [PATCH 131/178] chore: clean tests --- configs/config_mainnet.py | 10 +--------- tests/snapshot/test_first_slots.py | 6 +++--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index aa513ccca..4d40b52b4 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -523,22 +523,14 @@ } } -# Lido V3 - stVaults ---------------------------------------------------------- -# ----------------------------------------------------------------------------- - +# Lido V3 - stVaults VAULT_HUB = "0x1d201BE093d847f6446530Efb0E8Fb426d176709" - ACCOUNTING = "0x23ED611be0e1a820978875C0122F92260804cdDf" ACCOUNTING_IMPL = "0xd43a3E984071F40d5d840f60708Af0e9526785df" - OPERATOR_GRID = "0xC69685E89Cefc327b43B7234AC646451B27c544d" - LAZY_ORACLE = "0x5DB427080200c235F2Ae8Cd17A7be87921f7AD6c" - PREDEPOSIT_GUARANTEE = "0xF4bF42c6D6A0E38825785048124DBAD6c9eaaac3" - VAULTS_ADAPTER = "0xe2DE6d2DefF15588a71849c0429101F8ca9FB14D" - GATE_SEAL_V3 = "0x881dAd714679A6FeaA636446A0499101375A365c" INITIAL_MAX_EXTERNAL_RATIO_BP = 300 # 3% diff --git a/tests/snapshot/test_first_slots.py b/tests/snapshot/test_first_slots.py index 44754c252..15121f7d0 100644 --- a/tests/snapshot/test_first_slots.py +++ b/tests/snapshot/test_first_slots.py @@ -49,11 +49,11 @@ def test_first_slots(sandwich_upgrade: SandwichFn): def skip_slots() -> Sequence[tuple[str, int]]: """Slots that are not checked for equality""" return [ - # adding 9 new easy track factories + # Adding 8 new easy track factories - steps 2-9 of V3 upgrade (contracts.easy_track.address, 0x05), - # new slot in the contract _nodeOperatorSummary + # Set soft-mode target validators limit to 0 for operator A41 - step 1.3 of operations voting (contracts.node_operators_registry.address, 0x01), - # finance slot changing due to deposit bot funding / limits updates + # Transfer MATIC from Treasury to LOL Multisig - step 7 of operations voting (contracts.finance.address, 0x07), ] From df3476b7e1691b90c82ec430b87e1afec1288443 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sat, 6 Dec 2025 17:49:15 +0000 Subject: [PATCH 132/178] fix: CI branches --- .github/workflows/core_tests.yml | 1 - .github/workflows/dual_governance_regression.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 3e6a43c90..a702ede19 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -10,7 +10,6 @@ on: - "feat/rc1" - "feat/next-vote" - "feat/v3-vote" - - "feat/v3-vote-ci" schedule: - cron: "0 0 * * TUE" diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index 2f90d4644..2b7656567 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -13,7 +13,7 @@ on: - "feat/rc2" - "feat/rc1" - "feat/next-vote" - - "feat/v3-vote-ci" + - "feat/v3-vote" workflow_dispatch: jobs: From 2e97644a2abc2ffaa887c49cabcad439172e2993 Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 8 Dec 2025 11:38:49 +0300 Subject: [PATCH 133/178] test: add 1.2 state checks --- tests/utils_test_2025_12_10_lidov3.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index 7243faaa7..98c8b2fe1 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -617,6 +617,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): staking_router = interface.StakingRouter(STAKING_ROUTER) old_burner = interface.Burner(OLD_BURNER) oracle_daemon_config = interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) + upgradeTemplate = interface.UpgradeTemplateV3(UPGRADE_TEMPLATE) # Save original implementations for comparison locator_impl_before = get_ossifiable_proxy_impl(LIDO_LOCATOR) @@ -633,6 +634,12 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # ================== DG before proposal executed checks =================== # ========================================================================= + # Step 1.2. Call V3Template.startUpgrade + assert upgradeTemplate.upgradeBlockNumber() == 0, "V3Template should have upgradeBlockNumber 0 before startUpgrade" + assert upgradeTemplate.initialTotalShares() == 0, "V3Template should have initialTotalShares 0 before startUpgrade" + assert upgradeTemplate.initialTotalPooledEther() == 0, "V3Template should have initialTotalPooledEther 0 before startUpgrade" + assert upgradeTemplate.initialOldBurnerStethSharesBalance() == 0, "V3Template should have initialOldBurnerStethSharesBalance 0 before startUpgrade" + # Step 1.3: Check Lido Locator implementation initial state assert locator_impl_before != LIDO_LOCATOR_IMPL, "Locator implementation should be different before upgrade" @@ -843,6 +850,11 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # ==================== After DG proposal executed checks ================== # ========================================================================= + # Step 1.2. Call V3Template.startUpgrade + assert upgradeTemplate.upgradeBlockNumber() != 0, "V3Template should have upgradeBlockNumber not 0 after startUpgrade" + assert upgradeTemplate.initialTotalShares() != 0, "V3Template should have initialTotalShares not 0 after startUpgrade" + assert upgradeTemplate.initialTotalPooledEther() != 0, "V3Template should have initialTotalPooledEther not 0 after startUpgrade" + # Step 1.3: Validate Lido Locator implementation was updated assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" From 8c437e7299a43fd6b75176fc15856bfbc1c1c49d Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 8 Dec 2025 14:24:57 +0300 Subject: [PATCH 134/178] test: review fixes --- configs/config_mainnet.py | 1 + interfaces/UpgradeableBeacon.json | 142 ++++++++++++++++++ .../test_oracle_report_with_notifier.py | 18 +++ tests/regression/test_pause_resume.py | 48 +++++- tests/regression/test_permissions.py | 20 ++- tests/snapshot/test_dsm.py | 2 +- tests/snapshot/test_first_slots.py | 2 +- tests/snapshot/test_lido_snapshot.py | 2 - tests/snapshot/test_plain_submit.py | 2 - tests/utils_test_2025_12_10_lidov3.py | 15 +- utils/config.py | 4 + 11 files changed, 243 insertions(+), 13 deletions(-) create mode 100644 interfaces/UpgradeableBeacon.json diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 4d40b52b4..5c63a9cd2 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -532,5 +532,6 @@ PREDEPOSIT_GUARANTEE = "0xF4bF42c6D6A0E38825785048124DBAD6c9eaaac3" VAULTS_ADAPTER = "0xe2DE6d2DefF15588a71849c0429101F8ca9FB14D" GATE_SEAL_V3 = "0x881dAd714679A6FeaA636446A0499101375A365c" +STAKING_VAULT_BEACON = "0x5FbE8cEf9CCc56ad245736D3C5bAf82ad54Ca789" INITIAL_MAX_EXTERNAL_RATIO_BP = 300 # 3% diff --git a/interfaces/UpgradeableBeacon.json b/interfaces/UpgradeableBeacon.json new file mode 100644 index 000000000..ada815fee --- /dev/null +++ b/interfaces/UpgradeableBeacon.json @@ -0,0 +1,142 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + }, + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "BeaconInvalidImplementation", + "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": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "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" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/tests/regression/test_oracle_report_with_notifier.py b/tests/regression/test_oracle_report_with_notifier.py index 4b283a71e..5daabf7a2 100644 --- a/tests/regression/test_oracle_report_with_notifier.py +++ b/tests/regression/test_oracle_report_with_notifier.py @@ -50,6 +50,24 @@ def test_oracle_report_revert(): oracle_report(cl_diff=0, report_el_vault=True, report_withdrawals_vault=False) +def test_only_accounting_can_call_handle_post_token_rebase(): + """Test that only Accounting can call TokenRateNotifier.handlePostTokenRebase""" + + # Any non-Accounting address should not be allowed to call handlePostTokenRebase + # Use some sane values for the call; the exact numbers are irrelevant, it should revert on caller + with reverts(encode_error("ErrorNotAuthorizedRebaseCaller()")): + contracts.token_rate_notifier.handlePostTokenRebase( + 0, # report_timestamp, + 0, # time_elapsed, + 0, # pre_total_shares, + 0, # pre_total_ether, + 0, # post_total_shares, + 0, # post_total_ether, + 0, # shares_minted_as_fees, + {"from": accounts[0]}, + ) + + def test_oracle_report_pushes_rate(): """Test oracle report emits cross domain messenger event""" diff --git a/tests/regression/test_pause_resume.py b/tests/regression/test_pause_resume.py index fffc0938e..902223eca 100644 --- a/tests/regression/test_pause_resume.py +++ b/tests/regression/test_pause_resume.py @@ -220,7 +220,7 @@ def test_resumed_staking_can_stake(stranger): stranger.transfer(contracts.lido, DEPOSIT_AMOUNT) @pytest.mark.usefixtures("stopped_lido") -def test_resumed_staking_cant_deposit(): +def test_cant_resume_staking_on_stopped_lido(): with brownie.reverts("CONTRACT_IS_STOPPED"): contracts.lido.resumeStaking({"from": contracts.agent}), @@ -268,7 +268,6 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.DepositsPaused, {"from": stranger}) (report_tx, _) = oracle_report() - # Note: why we use TransferShares event in this test - https://github.com/lidofinance/core/issues/1565 # print(report_tx.events["TransferShares"]) # zero index - mint to accounting contract, 1 index - module, 2 index - simple dvt, 3 index - csm @@ -328,6 +327,51 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): assert report_tx.events["TransferShares"][simple_dvt_index]["sharesValue"] > 0 assert report_tx.events["TransferShares"][csm_index]["sharesValue"] > 0 + # do the same checks for Transfer event ------------------------------------------------------- + + assert report_tx.events["Transfer"][module_index]["to"] == module_address + assert report_tx.events["Transfer"][module_index]["from"] == contracts.accounting.address + assert report_tx.events["Transfer"][simple_dvt_index]["to"] == contracts.simple_dvt.address + assert report_tx.events["Transfer"][simple_dvt_index]["from"] == contracts.accounting.address + assert report_tx.events["Transfer"][csm_index]["to"] == contracts.csm.address + assert report_tx.events["Transfer"][csm_index]["from"] == contracts.accounting.address + assert report_tx.events["Transfer"][agent_index]["to"] == contracts.agent + assert report_tx.events["Transfer"][agent_index]["from"] == contracts.accounting.address + + # module_treasury_fee = module_share / share_pct * treasury_pct + module_treasury_fee = ( + report_tx.events["Transfer"][module_index]["value"] + * 100_00 + // module_stats["stakingModuleFee"] + * module_stats["treasuryFee"] + // 100_00 + ) + simple_dvt_stats = contracts.staking_router.getStakingModule(2) + simple_dvt_treasury_fee = ( + report_tx.events["Transfer"][simple_dvt_index]["value"] + * 100_00 + // simple_dvt_stats["stakingModuleFee"] + * simple_dvt_stats["treasuryFee"] + // 100_00 + ) + csm_stats = contracts.staking_router.getStakingModule(3) + csm_treasury_fee = ( + report_tx.events["Transfer"][csm_index]["value"] + * 100_00 + // csm_stats["stakingModuleFee"] + * csm_stats["treasuryFee"] + // 100_00 + ) + + assert almostEqWithDiff( + module_treasury_fee + simple_dvt_treasury_fee + csm_treasury_fee, + report_tx.events["Transfer"][agent_index]["value"], + 100, + ) + assert report_tx.events["Transfer"][module_index]["value"] > 0 + assert report_tx.events["Transfer"][simple_dvt_index]["value"] > 0 + assert report_tx.events["Transfer"][csm_index]["value"] > 0 + def test_stopped_staking_module_cant_stake(stranger): contracts.staking_router.grantRole( diff --git a/tests/regression/test_permissions.py b/tests/regression/test_permissions.py index 4152e4c9c..c32595189 100644 --- a/tests/regression/test_permissions.py +++ b/tests/regression/test_permissions.py @@ -70,7 +70,9 @@ ACCOUNTING, PREDEPOSIT_GUARANTEE, VAULTS_ADAPTER, - GATE_SEAL_V3 + GATE_SEAL_V3, + L1_TOKEN_RATE_NOTIFIER, + STAKING_VAULT_BEACON ) @@ -572,7 +574,21 @@ def protocol_permissions(): "PAUSE_ROLE": "PausableUntilWithRoles.PauseRole", "RESUME_ROLE": "PausableUntilWithRoles.ResumeRole", }, - } + }, + L1_TOKEN_RATE_NOTIFIER: { + "contract_name": "TokenRateNotifier", + "contract": contracts.token_rate_notifier, + "type": "CustomApp", + "state": {"owner": contracts.agent}, + "roles": {}, + }, + STAKING_VAULT_BEACON: { + "contract_name": "UpgradeableBeacon", + "contract": contracts.staking_vault_beacon, + "type": "CustomApp", + "state": {"owner": contracts.agent}, + "roles": {}, + }, } diff --git a/tests/snapshot/test_dsm.py b/tests/snapshot/test_dsm.py index 4d3df33b3..22d7521e3 100644 --- a/tests/snapshot/test_dsm.py +++ b/tests/snapshot/test_dsm.py @@ -223,7 +223,7 @@ def far_block() -> int: @pytest.fixture(scope="module") def far_ts() -> int: - return chain.time() + 30 * 24 * 60 * 60 # 30 days + return chain.time() + 21 * 24 * 60 * 60 # 21 days def _sleep_till_block(block: int, ts: int) -> None: diff --git a/tests/snapshot/test_first_slots.py b/tests/snapshot/test_first_slots.py index 15121f7d0..f2cca50f7 100644 --- a/tests/snapshot/test_first_slots.py +++ b/tests/snapshot/test_first_slots.py @@ -160,7 +160,7 @@ def far_block() -> int: @pytest.fixture(scope="module") def far_ts() -> int: - return chain.time() + 30 * 24 * 60 * 60 # 30 days + return chain.time() + 21 * 24 * 60 * 60 # 21 days def _sleep_till_block(block: int, ts: int) -> None: diff --git a/tests/snapshot/test_lido_snapshot.py b/tests/snapshot/test_lido_snapshot.py index 38afe479f..14149f93f 100644 --- a/tests/snapshot/test_lido_snapshot.py +++ b/tests/snapshot/test_lido_snapshot.py @@ -36,8 +36,6 @@ class Frame(TypedDict): EXPECTED_SNAPSHOT_DIFFS: dict[str, Any] = { - "canPerform()": (True, False), - "getRecoveryVault": (AGENT, ZERO_ADDRESS), "lido.Lido.beaconBalance": ZERO_BYTES32, "lido.Lido.beaconValidators": ZERO_BYTES32, "lido.Lido.bufferedEther": ZERO_BYTES32, diff --git a/tests/snapshot/test_plain_submit.py b/tests/snapshot/test_plain_submit.py index 60eac0875..b53ef7395 100644 --- a/tests/snapshot/test_plain_submit.py +++ b/tests/snapshot/test_plain_submit.py @@ -86,8 +86,6 @@ def steps() -> Dict[str, Dict[str, any]]: step_diffs: Dict[str, Dict[str, ValueChanged]] = {} expected_diffs = { - "canPerform()": ValueChanged(from_val=True, to_val=False), - "getRecoveryVault()": ValueChanged(from_val=contracts.agent.address, to_val=ZERO_ADDRESS), } for step, pair_of_snapshots in dict_zip(before, after).items(): diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index 98c8b2fe1..4f7a5e6d1 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -1,4 +1,4 @@ -from brownie import chain, interface, web3, convert +from brownie import chain, interface, web3, convert, accounts, reverts from brownie.network.transaction import TransactionReceipt from utils.test.tx_tracing_helpers import ( @@ -8,7 +8,7 @@ display_voting_events, display_dg_events ) -from utils.evm_script import encode_call_script +from utils.evm_script import encode_call_script, encode_error from utils.voting import find_metadata_by_vote_id from utils.ipfs import get_lido_vote_cid_from_str from utils.dual_governance import PROPOSAL_STATUS, wait_for_target_time_to_satisfy_time_constrains @@ -53,7 +53,6 @@ ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" HASH_CONSENSUS = "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288" # HashConsensus for AccountingOracle STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" -ACL = "0x9895F0F17cc1d1891b6f18ee0b483B6f221b37Bb" ORACLE_DAEMON_CONFIG = "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09" CSM_ACCOUNTING = "0x4d72BFF1BeaC69925F8Bd12526a39BAAb069e5Da" OLD_BURNER = "0xD15a672319Cf0352560eE76d9e89eAB0889046D3" @@ -188,6 +187,8 @@ def validate_upgrade_finished_events(events) -> None: # Transfer and TransferShares events are emitted only if old Burner has some shares on balance if events.count("Transfer") > 0: + assert events.count("Transfer") == 1, "Transfer event should be emitted only once" + assert events.count("TransferShares") == 1, "TransferShares event should be emitted only once" assert convert.to_address(events["Transfer"][0]["from"]) == OLD_BURNER, f"Wrong from: expected {OLD_BURNER}" assert convert.to_address(events["Transfer"][0]["to"]) == BURNER, f"Wrong to: expected {BURNER}" assert convert.to_address(events["Transfer"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" @@ -699,6 +700,8 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert accounting_oracle.getContractVersion() == NEW_ACCOUNTING_ORACLE_VERSION - 1, "AccountingOracle should have version 3 before finishUpgrade" assert accounting_oracle.getConsensusVersion() == NEW_HASH_CONSENSUS_VERSION - 1, "HashConsensus should have version 4 before finishUpgrade" + assert upgradeTemplate.isUpgradeFinished() == False, "V3Template should have isUpgradeFinished False before finishUpgrade" + if details["status"] == PROPOSAL_STATUS["submitted"]: chain.sleep(timelock.getAfterSubmitDelay() + 1) dual_governance.scheduleProposal(expected_dg_proposal_id, {"from": stranger}) @@ -906,3 +909,9 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): accounting_oracle = interface.AccountingOracle(ACCOUNTING_ORACLE) assert accounting_oracle.getContractVersion() == NEW_ACCOUNTING_ORACLE_VERSION, "AccountingOracle should have version 4 after finishUpgrade" assert accounting_oracle.getConsensusVersion() == NEW_HASH_CONSENSUS_VERSION, "HashConsensus should have version 5 after finishUpgrade" + assert upgradeTemplate.isUpgradeFinished() == True, "V3Template should have isUpgradeFinished True after finishUpgrade" + + # Check that a second call to finishUpgrade reverts + agent_account = accounts.at(AGENT, force=True) + with reverts(encode_error("UpgradeAlreadyFinished()")): + upgradeTemplate.finishUpgrade({"from": agent_account}) diff --git a/utils/config.py b/utils/config.py index 8619f1220..23b581247 100644 --- a/utils/config.py +++ b/utils/config.py @@ -330,6 +330,10 @@ def lazy_oracle(self) -> interface.LazyOracle: def predeposit_guarantee(self) -> interface.PredepositGuarantee: return interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) + @property + def staking_vault_beacon(self) -> interface.UpgradeableBeacon: + return interface.UpgradeableBeacon(STAKING_VAULT_BEACON) + @property def lido_locator(self) -> interface.LidoLocator: return interface.LidoLocator(LIDO_LOCATOR) From d5fbdfeefd42d8977022416bd9131e353231cbbf Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 8 Dec 2025 14:46:19 +0300 Subject: [PATCH 135/178] test: add periodsLength var handle --- tests/snapshot/test_first_slots.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/snapshot/test_first_slots.py b/tests/snapshot/test_first_slots.py index f2cca50f7..0866aea18 100644 --- a/tests/snapshot/test_first_slots.py +++ b/tests/snapshot/test_first_slots.py @@ -55,6 +55,8 @@ def skip_slots() -> Sequence[tuple[str, int]]: (contracts.node_operators_registry.address, 0x01), # Transfer MATIC from Treasury to LOL Multisig - step 7 of operations voting (contracts.finance.address, 0x07), + # periodsLength var - step 7 of operations voting can trigger _newPeriod in Finance + (contracts.finance.address, 0x09), ] From e4346adfb5fec0530f4c2114f533e0348b206c86 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Mon, 8 Dec 2025 13:59:51 +0000 Subject: [PATCH 136/178] fix: trp tests --- tests/utils_test_2025_12_10_operations.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py index 199ed4442..a93fba9aa 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_10_operations.py @@ -966,23 +966,15 @@ def trp_limit_test(stranger): easy_track = interface.EasyTrack(EASY_TRACK) ldo_token = interface.ERC20(LDO_TOKEN) - to_spend = TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + to_spend = 15_000_000 * 10**18 max_spend_at_once = 5_000_000 * 10**18 trp_committee_account = accounts.at(TRP_COMMITTEE, force=True) chain.snapshot() - # check that there is no way to spend more then expected - with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): - create_and_enact_payment_motion( - easy_track, - TRP_COMMITTEE, - TRP_TOP_UP_EVM_SCRIPT_FACTORY, - ldo_token, - [trp_committee_account], - [to_spend + 1], - stranger, - ) + # sleep to January so that TRP limit period does not change (it depends when tests are run) + chain.sleep(30 * 24 * 60 * 60) + chain.mine() # spend all in several transfers recipients = [] From 18b3e237702432014d2a817434387d1a0effc769 Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 8 Dec 2025 17:57:57 +0300 Subject: [PATCH 137/178] feat: update vote script address --- interfaces/IVaultsAdapter.json | 35 ++++-------------------- scripts/upgrade_2025_12_10_mainnet_v3.py | 2 +- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/interfaces/IVaultsAdapter.json b/interfaces/IVaultsAdapter.json index 661ae1a56..6e85089d7 100644 --- a/interfaces/IVaultsAdapter.json +++ b/interfaces/IVaultsAdapter.json @@ -10,11 +10,6 @@ "internalType": "bytes", "name": "_pubkeys", "type": "bytes" - }, - { - "internalType": "address", - "name": "_feeRecipient", - "type": "address" } ], "name": "forceValidatorExit", @@ -22,24 +17,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "_vault", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_liabilitySharesTarget", - "type": "uint256" - } - ], - "name": "setLiabilitySharesTarget", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -89,19 +66,19 @@ "type": "address" }, { - "internalType": "uint16", + "internalType": "uint256", "name": "_infrastructureFeeBP", - "type": "uint16" + "type": "uint256" }, { - "internalType": "uint16", + "internalType": "uint256", "name": "_liquidityFeeBP", - "type": "uint16" + "type": "uint256" }, { - "internalType": "uint16", + "internalType": "uint256", "name": "_reservationFeeBP", - "type": "uint16" + "type": "uint256" } ], "name": "updateVaultFees", diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index 8b86726b9..ff821818d 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -45,7 +45,7 @@ # ============================== Addresses =================================== -OMNIBUS_CONTRACT = "0xa47Ca1d2029D8e735237ea4E74c607426d4aA07e" +OMNIBUS_CONTRACT = "0xf2BeCc7aF0AA50DaD54781e06d5ce1A7eAD59AfA" # ============================= Description ================================== From e28f6d32062daedb3b6df19c3f0fce82e7f00b3c Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 8 Dec 2025 18:45:42 +0300 Subject: [PATCH 138/178] test: new gate seal cases --- tests/regression/test_gate_seal.py | 70 ++++++++++++++++++++++++++- tests/utils_test_2025_12_10_lidov3.py | 6 +++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/tests/regression/test_gate_seal.py b/tests/regression/test_gate_seal.py index e0425b9fe..f5df2c0ba 100644 --- a/tests/regression/test_gate_seal.py +++ b/tests/regression/test_gate_seal.py @@ -1,6 +1,6 @@ import pytest -from brownie import reverts, accounts, chain, web3, Wei # type: ignore +from brownie import reverts, accounts, chain, web3, Wei, interface # type: ignore from eth_hash.auto import keccak from utils.test.exit_bus_data import LidoValidator @@ -24,6 +24,9 @@ CHAIN_SLOTS_PER_EPOCH, CHAIN_SECONDS_PER_SLOT, AO_EPOCHS_PER_FRAME, + VAULT_HUB, + PREDEPOSIT_GUARANTEE, + GATE_SEAL_V3, ) @@ -42,6 +45,20 @@ def test_gate_seal_expiration(gate_seal_committee): contracts.gate_seal.seal([WITHDRAWAL_QUEUE], {"from": gate_seal_committee}) +def test_gate_seal_v3_expiration(gate_seal_committee): + gate_seal_v3 = interface.GateSeal(GATE_SEAL_V3) + + assert not gate_seal_v3.is_expired() + time = chain.time() + expiry = gate_seal_v3.get_expiry_timestamp() + chain.sleep(expiry - time + 1) + chain.mine(1) + + assert gate_seal_v3.is_expired() + with reverts("gate seal: expired"): + gate_seal_v3.seal(gate_seal_v3.get_sealables(), {"from": gate_seal_committee}) + + def test_gate_seal_twg_veb_expiration(gate_seal_committee): assert not contracts.veb_twg_gate_seal.is_expired() time = chain.time() @@ -326,6 +343,57 @@ def test_gate_seal_twg_veb_scenario(steth_holder, gate_seal_committee, eth_whale pubkey_hex = "0x" + pubkey_bytes.hex() assert validator_key == pubkey_hex + +def test_gate_seal_v3_vaults_scenario(gate_seal_committee): + gate_seal_v3 = interface.GateSeal(GATE_SEAL_V3) + + assert not gate_seal_v3.is_expired() + + sealables = gate_seal_v3.get_sealables() + assert len(sealables) == 2 + assert contracts.vault_hub.address in sealables + assert contracts.predeposit_guarantee.address in sealables + + pause_duration = gate_seal_v3.get_seal_duration_seconds() + + assert not contracts.vault_hub.isPaused() + assert not contracts.predeposit_guarantee.isPaused() + + seal_tx = gate_seal_v3.seal(sealables, {"from": gate_seal_committee}) + + assert seal_tx.events.count("Sealed") == len(sealables) + for seal_event in seal_tx.events["Sealed"]: + assert seal_event["gate_seal"] == gate_seal_v3.address + assert seal_event["sealed_for"] == pause_duration + assert seal_event["sealed_by"] == gate_seal_committee + assert seal_event["sealable"] in sealables + assert seal_event["sealed_at"] == seal_tx.timestamp + + for pause_event in seal_tx.events["Paused"]: + assert pause_event["duration"] == pause_duration + + assert gate_seal_v3.is_expired() + with reverts("gate seal: expired"): + gate_seal_v3.seal(sealables, {"from": gate_seal_committee}) + + assert contracts.vault_hub.isPaused() + assert ( + contracts.vault_hub.getResumeSinceTimestamp() + == seal_tx.timestamp + pause_duration + ) + + assert contracts.predeposit_guarantee.isPaused() + assert ( + contracts.predeposit_guarantee.getResumeSinceTimestamp() + == seal_tx.timestamp + pause_duration + ) + + chain.sleep(pause_duration + 1) + chain.mine(1) + + assert not contracts.vault_hub.isPaused() + assert not contracts.predeposit_guarantee.isPaused() + def _wait_for_next_ref_slot(): wait_to_next_available_report_time(contracts.hash_consensus_for_validators_exit_bus_oracle) ref_slot, _ = contracts.hash_consensus_for_validators_exit_bus_oracle.getCurrentFrame() diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index 4f7a5e6d1..f24e58f3e 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -79,6 +79,7 @@ ACCOUNTING_ORACLE_IMPL = "0x1455B96780A93e08abFE41243Db92E2fCbb0141c" RESEAL_MANAGER = "0x7914b5a1539b97Bd0bbd155757F25FD79A522d24" BURNER = "0xE76c52750019b80B43E36DF30bf4060EB73F573a" +VAULTS_FACTORY = "0x02Ca7772FF14a9F6c1a08aF385aA96bb1b34175A" # New Easy Track factories ALTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0xa29173C7BCf39dA48D5E404146A652d7464aee14" @@ -416,6 +417,7 @@ def enact_and_test_voting( accounts, ldo_holder, vote_ids_from_env, + stranger, expected_vote_id, expected_dg_proposal_id, ): @@ -503,6 +505,10 @@ def enact_and_test_voting( assert UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY factory after vote" assert UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY factory after vote" + # Check that after the Aragon vote has passed, creation of the vaults via VaultFactory still reverts + with reverts(): + interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard(stranger, stranger, stranger, 100, 100, [], {"from": stranger}) + vote_events = group_voting_events_from_receipt(vote_tx) assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT From b2fda9532870505db0b1417229c308a5785e010a Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 8 Dec 2025 19:23:41 +0300 Subject: [PATCH 139/178] test: fix stranger issue --- interfaces/VaultFactory.json | 301 +++++++++++++++++++++++++++++++++++ tests/test_2025_12_10.py | 14 +- 2 files changed, 308 insertions(+), 7 deletions(-) create mode 100644 interfaces/VaultFactory.json diff --git a/interfaces/VaultFactory.json b/interfaces/VaultFactory.json new file mode 100644 index 000000000..c6f34d9c1 --- /dev/null +++ b/interfaces/VaultFactory.json @@ -0,0 +1,301 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_lidoLocator", + "type": "address" + }, + { + "internalType": "address", + "name": "_beacon", + "type": "address" + }, + { + "internalType": "address", + "name": "_dashboardImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "_previousFactory", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CloneArgumentsTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "FailedDeployment", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientFunds", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "argument", + "type": "string" + } + ], + "name": "ZeroArgument", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "dashboard", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "admin", + "type": "address" + } + ], + "name": "DashboardCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultCreated", + "type": "event" + }, + { + "inputs": [], + "name": "BEACON", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DASHBOARD_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PREVIOUS_FACTORY", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_defaultAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "address", + "name": "_nodeOperatorManager", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nodeOperatorFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_confirmExpiry", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "internalType": "struct Permissions.RoleAssignment[]", + "name": "_roleAssignments", + "type": "tuple[]" + } + ], + "name": "createVaultWithDashboard", + "outputs": [ + { + "internalType": "contract IStakingVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "contract Dashboard", + "name": "dashboard", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_defaultAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "address", + "name": "_nodeOperatorManager", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nodeOperatorFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_confirmExpiry", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "internalType": "struct Permissions.RoleAssignment[]", + "name": "_roleAssignments", + "type": "tuple[]" + } + ], + "name": "createVaultWithDashboardWithoutConnectingToVaultHub", + "outputs": [ + { + "internalType": "contract IStakingVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "contract Dashboard", + "name": "dashboard", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "deployedVaults", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 791b2afdb..c03ac68bb 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -9,7 +9,7 @@ def isolation(): def test_vote_v1_v2_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, 194, 6, ) @@ -23,7 +23,7 @@ def test_vote_v1_v2_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, st def test_vote_v1_v2_dg2_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, 194, 6, ) @@ -37,7 +37,7 @@ def test_vote_v1_v2_dg2_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, st def test_vote_v1_dg1_v2_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, 194, 6, ) @@ -55,7 +55,7 @@ def test_vote_v2_v1_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, st 194, 6, ) - lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, 195, 7, ) @@ -69,7 +69,7 @@ def test_vote_v2_v1_dg2_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, st 194, 6, ) - lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, 195, 7, ) @@ -85,8 +85,8 @@ def test_vote_v2_dg2_v1_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, st ops.enact_and_test_dg(stranger, 6) - lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, 195, 7, ) - lidov3.enact_and_test_dg(stranger, 7) \ No newline at end of file + lidov3.enact_and_test_dg(stranger, 7) From e2764c9283eecfdcfe42a8038c7b9118a33d24f8 Mon Sep 17 00:00:00 2001 From: dry914 Date: Mon, 8 Dec 2025 20:14:34 +0300 Subject: [PATCH 140/178] tests: review fixes --- tests/regression/test_gate_seal.py | 4 ++-- tests/utils_test_2025_12_10_lidov3.py | 31 +++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/tests/regression/test_gate_seal.py b/tests/regression/test_gate_seal.py index f5df2c0ba..4ded3162d 100644 --- a/tests/regression/test_gate_seal.py +++ b/tests/regression/test_gate_seal.py @@ -362,11 +362,11 @@ def test_gate_seal_v3_vaults_scenario(gate_seal_committee): seal_tx = gate_seal_v3.seal(sealables, {"from": gate_seal_committee}) assert seal_tx.events.count("Sealed") == len(sealables) - for seal_event in seal_tx.events["Sealed"]: + for i, seal_event in enumerate(seal_tx.events["Sealed"]): assert seal_event["gate_seal"] == gate_seal_v3.address assert seal_event["sealed_for"] == pause_duration assert seal_event["sealed_by"] == gate_seal_committee - assert seal_event["sealable"] in sealables + assert seal_event["sealable"] == sealables[i] assert seal_event["sealed_at"] == seal_tx.timestamp for pause_event in seal_tx.events["Paused"]: diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index f24e58f3e..4e8113e60 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -507,7 +507,15 @@ def enact_and_test_voting( # Check that after the Aragon vote has passed, creation of the vaults via VaultFactory still reverts with reverts(): - interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard(stranger, stranger, stranger, 100, 100, [], {"from": stranger}) + interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, + [], + {"from": stranger, "value": "1 ether"}, + ) vote_events = group_voting_events_from_receipt(vote_tx) assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT @@ -625,6 +633,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): old_burner = interface.Burner(OLD_BURNER) oracle_daemon_config = interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) upgradeTemplate = interface.UpgradeTemplateV3(UPGRADE_TEMPLATE) + lido = interface.Lido(LIDO) # Save original implementations for comparison locator_impl_before = get_ossifiable_proxy_impl(LIDO_LOCATOR) @@ -646,6 +655,8 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert upgradeTemplate.initialTotalShares() == 0, "V3Template should have initialTotalShares 0 before startUpgrade" assert upgradeTemplate.initialTotalPooledEther() == 0, "V3Template should have initialTotalPooledEther 0 before startUpgrade" assert upgradeTemplate.initialOldBurnerStethSharesBalance() == 0, "V3Template should have initialOldBurnerStethSharesBalance 0 before startUpgrade" + initial_total_shares_before = lido.getTotalShares() + initial_total_pooled_ether_before = lido.getTotalPooledEther() # Step 1.3: Check Lido Locator implementation initial state assert locator_impl_before != LIDO_LOCATOR_IMPL, "Locator implementation should be different before upgrade" @@ -695,7 +706,6 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): pass # Expected to fail # Step 1.18. Call V3Template.finishUpgrade - lido = interface.Lido(LIDO) assert lido.getContractVersion() == NEW_LIDO_VERSION - 1, "LIDO should have version 2 before finishUpgrade" assert lido.allowance(CSM_ACCOUNTING, BURNER) == 0, "No allowance from CSM_ACCOUNTING to BURNER before finishUpgrade" assert lido.allowance(CSM_ACCOUNTING, OLD_BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from CSM_ACCOUNTING to OLD_BURNER before finishUpgrade" @@ -861,8 +871,8 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # Step 1.2. Call V3Template.startUpgrade assert upgradeTemplate.upgradeBlockNumber() != 0, "V3Template should have upgradeBlockNumber not 0 after startUpgrade" - assert upgradeTemplate.initialTotalShares() != 0, "V3Template should have initialTotalShares not 0 after startUpgrade" - assert upgradeTemplate.initialTotalPooledEther() != 0, "V3Template should have initialTotalPooledEther not 0 after startUpgrade" + assert upgradeTemplate.initialTotalShares() == initial_total_shares_before, "V3Template should have initialTotalShares equal to the initial total shares before upgrade" + assert upgradeTemplate.initialTotalPooledEther() == initial_total_pooled_ether_before, "V3Template should have initialTotalPooledEther equal to the initial total pooled ether before upgrade" # Step 1.3: Validate Lido Locator implementation was updated assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" @@ -921,3 +931,16 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): agent_account = accounts.at(AGENT, force=True) with reverts(encode_error("UpgradeAlreadyFinished()")): upgradeTemplate.finishUpgrade({"from": agent_account}) + + # Check that after the DG proposal has passed, creation of the vaults via VaultFactory can be done + creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": "1 ether"}, + ) + assert creation_tx.events.count("VaultCreated") == 1 + assert creation_tx.events.count("DashboardCreated") == 1 From bfbcde07b055c7b46f224885c944f50c28a7ac42 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 9 Dec 2025 01:42:36 +0300 Subject: [PATCH 141/178] test: add ET tests --- tests/utils_test_2025_12_10_lidov3.py | 377 +++++++++++++++++++++++++- 1 file changed, 376 insertions(+), 1 deletion(-) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index 4e8113e60..87f9cb169 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -1,4 +1,4 @@ -from brownie import chain, interface, web3, convert, accounts, reverts +from brownie import chain, interface, web3, convert, accounts, reverts, ZERO_ADDRESS from brownie.network.transaction import TransactionReceipt from utils.test.tx_tracing_helpers import ( @@ -22,6 +22,7 @@ from utils.test.event_validators.proxy import validate_proxy_upgrade_event from utils.test.event_validators.permission import validate_grant_role_event, validate_revoke_role_event from utils.test.event_validators.aragon import validate_aragon_set_app_event, validate_aragon_grant_permission_event, validate_aragon_revoke_permission_event +from utils.test.easy_track_helpers import _encode_calldata, create_and_enact_motion # ============================================================================ @@ -82,6 +83,7 @@ VAULTS_FACTORY = "0x02Ca7772FF14a9F6c1a08aF385aA96bb1b34175A" # New Easy Track factories +ST_VAULTS_COMMITTEE = "0x18A1065c81b0Cc356F1b1C843ddd5E14e4AefffF" ALTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0xa29173C7BCf39dA48D5E404146A652d7464aee14" REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY = "0x194A46DA1947E98c9D79af13E06Cfbee0D8610cC" REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0x5292A1284e4695B95C0840CF8ea25A818751C17F" @@ -944,3 +946,376 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): ) assert creation_tx.events.count("VaultCreated") == 1 assert creation_tx.events.count("DashboardCreated") == 1 + + # Scenario tests for Easy Track factories behavior after the vote + trusted_address = accounts.at(ST_VAULTS_COMMITTEE, force=True) + easy_track = interface.EasyTrack(EASYTRACK) + test_register_groups_in_operator_grid(easy_track, trusted_address, stranger) + test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger) + test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger) + test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger) + test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger) + test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger) + test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger) + test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger) + + +def test_register_groups_in_operator_grid(easy_track, trusted_address, stranger): + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + + operator_addresses = [ + "0x0000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000002", + ] + share_limits = [1000, 5000] + tiers_params_array = [ + [(500, 200, 100, 50, 40, 10), (800, 200, 100, 50, 40, 10)], + [(800, 200, 100, 50, 40, 10), (800, 200, 100, 50, 40, 10)], + ] + + calldata = _encode_calldata( + ["address[]", "uint256[]", "(uint256,uint256,uint256,uint256,uint256,uint256)[][]"], + [operator_addresses, share_limits, tiers_params_array] + ) + + # Check initial state + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert group[0] == ZERO_ADDRESS # operator + assert group[1] == 0 # shareLimit + assert len(group[3]) == 0 # tiersId array should be empty + + create_and_enact_motion(easy_track, trusted_address, REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert group[0] == operator_address # operator + assert group[1] == share_limits[i] # shareLimit + assert len(group[3]) == len(tiers_params_array[i]) # tiersId array should have the same length as tiers_params + + # Check tier details + for j, tier_id in enumerate(group[3]): + tier = operator_grid.tier(tier_id) + assert tier[1] == tiers_params_array[i][j][0] # shareLimit + assert tier[3] == tiers_params_array[i][j][1] # reserveRatioBP + assert tier[4] == tiers_params_array[i][j][2] # forcedRebalanceThresholdBP + assert tier[5] == tiers_params_array[i][j][3] # infraFeeBP + assert tier[6] == tiers_params_array[i][j][4] # liquidityFeeBP + assert tier[7] == tiers_params_array[i][j][5] # reservationFeeBP + + +def test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger): + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + + # Define operator addresses + operator_addresses = [ + "0x0000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000004" + ] + + # Define tier parameters for each operator + tiers_params_array = [ + [ # Tiers for operator 1 + (500, 200, 100, 50, 40, 10), + (300, 150, 75, 25, 20, 5), + ], + [ # Tiers for operator 2 + (800, 250, 125, 60, 50, 15), + (400, 180, 90, 30, 25, 8), + ] + ] + + # First register the groups to add tiers to + executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) + for operator_address in operator_addresses: + operator_grid.registerGroup(operator_address, 1000, {"from": executor}) + + # Check initial state - no tiers + for operator_address in operator_addresses: + group = operator_grid.group(operator_address) + assert len(group[3]) == 0 # tiersId array should be empty + + calldata = _encode_calldata( + ["address[]", "(uint256,uint256,uint256,uint256,uint256,uint256)[][]"], + [operator_addresses, tiers_params_array] + ) + + create_and_enact_motion(easy_track, trusted_address, REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state - tiers should be registered + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert len(group[3]) == len(tiers_params_array[i]) # tiersId array should have the same length as tiers_params + + # Check tier details + for j, tier_id in enumerate(group[3]): + tier = operator_grid.tier(tier_id) + assert tier[1] == tiers_params_array[i][j][0] # shareLimit + assert tier[3] == tiers_params_array[i][j][1] # reserveRatioBP + assert tier[4] == tiers_params_array[i][j][2] # forcedRebalanceThresholdBP + assert tier[5] == tiers_params_array[i][j][3] # infraFeeBP + assert tier[6] == tiers_params_array[i][j][4] # liquidityFeeBP + assert tier[7] == tiers_params_array[i][j][5] # reservationFeeBP + + +def test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger): + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + + # Define new tier parameters + # (shareLimit, reserveRatioBP, forcedRebalanceThresholdBP, infraFeeBP, liquidityFeeBP, reservationFeeBP) + new_tier_params = [(2000, 300, 150, 75, 60, 20), (3000, 400, 200, 100, 80, 30)] + + # First register a group and tier to alter + executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) + operator_address = "0x0000000000000000000000000000000000000005" + operator_grid.registerGroup(operator_address, 10000, {"from": executor}) + initial_tier_params = [(1000, 200, 100, 50, 40, 10), (1000, 200, 100, 50, 40, 10)] + operator_grid.registerTiers(operator_address, initial_tier_params, {"from": executor}) + + tiers_count = operator_grid.tiersCount() + tier_ids = [tiers_count - 2, tiers_count - 1] + + # Check initial state + for i, tier_id in enumerate(tier_ids): + tier = operator_grid.tier(tier_id) + assert tier[1] == initial_tier_params[i][0] # shareLimit + assert tier[3] == initial_tier_params[i][1] # reserveRatioBP + assert tier[4] == initial_tier_params[i][2] # forcedRebalanceThresholdBP + assert tier[5] == initial_tier_params[i][3] # infraFeeBP + assert tier[6] == initial_tier_params[i][4] # liquidityFeeBP + assert tier[7] == initial_tier_params[i][5] # reservationFeeBP + + calldata = _encode_calldata(["uint256[]", "(uint256,uint256,uint256,uint256,uint256,uint256)[]"], [tier_ids, new_tier_params]) + + create_and_enact_motion(easy_track, trusted_address, ALTER_TIERS_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state + for i, tier_id in enumerate(tier_ids): + tier = operator_grid.tier(tier_id) + assert tier[1] == new_tier_params[i][0] # shareLimit + assert tier[3] == new_tier_params[i][1] # reserveRatioBP + assert tier[4] == new_tier_params[i][2] # forcedRebalanceThresholdBP + assert tier[5] == new_tier_params[i][3] # infraFeeBP + assert tier[6] == new_tier_params[i][4] # liquidityFeeBP + assert tier[7] == new_tier_params[i][5] # reservationFeeBP + + +def test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger): + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + + operator_addresses = ["0x0000000000000000000000000000000000000006", "0x0000000000000000000000000000000000000007"] + new_share_limits = [2000, 3000] + + # First register the group to update + executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) + for i, operator_address in enumerate(operator_addresses): + operator_grid.registerGroup(operator_address, new_share_limits[i]*2, {"from": executor}) + + # Check initial state + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert group[0] == operator_address # operator + assert group[1] == new_share_limits[i]*2 # shareLimit + + calldata = _encode_calldata( + ["address[]", "uint256[]"], + [operator_addresses, new_share_limits] + ) + + create_and_enact_motion(easy_track, trusted_address, UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert group[0] == operator_address # operator + assert group[1] == new_share_limits[i] # shareLimit + + +def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger): + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + + # First create the vaults + vaults = [] + for i in range(2): + creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": "1 ether"}, + ) + vaults.append(creation_tx.events["VaultCreated"][0]["vault"]) + + # Check initial state + for vault in vaults: + is_in_jail = operator_grid.isVaultInJail(vault) + assert is_in_jail == False + + calldata = _encode_calldata(["address[]", "bool[]"], [vaults, [True, True]]) + + create_and_enact_motion(easy_track, trusted_address, SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state + for i, vault in enumerate(vaults): + is_in_jail = operator_grid.isVaultInJail(vault) + assert is_in_jail == True + + +def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger): + vault_hub = interface.VaultHub(VAULT_HUB) + + # First create the vault + creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": "1 ether"}, + ) + vault = creation_tx.events["VaultCreated"][0]["vault"] + + # Check initial state + connection = vault_hub.vaultConnection(vault) + assert connection[6] != 1 # infraFeeBP + assert connection[7] != 1 # liquidityFeeBP + assert connection[8] == 0 # reservationFeeBP + + calldata = _encode_calldata(["address[]", "uint256[]", "uint256[]", "uint256[]"], [[vault], [1], [1], [0]]) + + motions_before = easy_track.getMotions() + tx = easy_track.createMotion(UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY, calldata, {"from": trusted_address}) + motions = easy_track.getMotions() + assert len(motions) == len(motions_before) + 1 + + ( + motion_id, + _, + _, + motion_duration, + motion_start_date, + _, + _, + _, + _, + ) = motions[-1] + + chain.mine(1, motion_start_date + motion_duration + 1) + + # bring fresh report for vault + current_time = chain.time() + accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) + interface.LazyOracle(LAZY_ORACLE).updateReportData( + current_time, + 1000, + "0x00", + "0x00", + {"from": accounting_oracle}) + + lazy_oracle = accounts.at(LAZY_ORACLE, force=True) + vault_hub.applyVaultReport( + vault, + current_time, + 2 * 10**18, + 2 * 10**18, + 0, + 0, + 0, + 0, + {"from": lazy_oracle}) + + easy_track.enactMotion( + motion_id, + tx.events["MotionCreated"]["_evmScriptCallData"], + {"from": stranger}, + ) + + # Check final state + connection = vault_hub.vaultConnection(vault) + assert connection[6] == 1 # infraFeeBP + assert connection[7] == 1 # liquidityFeeBP + assert connection[8] == 0 # reservationFeeBP + + +def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger): + vault_hub = interface.VaultHub(VAULT_HUB) + # top up VAULTS_ADAPTER + stranger.transfer(VAULTS_ADAPTER, 2 * 10**18) + + pubkey = b"01" * 48 + # First create the vault + creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": "1 ether"}, + ) + vault = creation_tx.events["VaultCreated"][0]["vault"] + + calldata = _encode_calldata(["address[]", "bytes[]"], [[vault], [pubkey]]) + + motions_before = easy_track.getMotions() + tx = easy_track.createMotion(FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY, calldata, {"from": trusted_address}) + motions = easy_track.getMotions() + assert len(motions) == len(motions_before) + 1 + + ( + motion_id, + _, + _, + motion_duration, + motion_start_date, + _, + _, + _, + _, + ) = motions[-1] + + chain.mine(1, motion_start_date + motion_duration + 1) + + # bring fresh report for vault + current_time = chain.time() + accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) + interface.LazyOracle(LAZY_ORACLE).updateReportData( + current_time, + 1000, + "0x00", + "0x00", + {"from": accounting_oracle}) + + # make vault unhealthy + lazy_oracle = accounts.at(LAZY_ORACLE, force=True) + vault_hub.applyVaultReport( + vault, + current_time, + 2 * 10**18, + 2 * 10**18, + 7 * 10**18, + 0, + 0, + 0, + {"from": lazy_oracle}) + + tx = easy_track.enactMotion( + motion_id, + tx.events["MotionCreated"]["_evmScriptCallData"], + {"from": stranger}, + ) + + # Check event was emitted + assert len(tx.events["ForcedValidatorExitTriggered"]) == 1 + event = tx.events["ForcedValidatorExitTriggered"][0] + assert event["vault"] == vault + assert event["pubkeys"] == "0x" + pubkey.hex() + assert event["refundRecipient"] == VAULTS_ADAPTER + + +def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): + vault_hub = interface.VaultHub(VAULT_HUB) From e1730eefcfab7724fd8ee7b6a23f8fca9df17574 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 9 Dec 2025 10:32:12 +0300 Subject: [PATCH 142/178] test: add chain revert --- tests/utils_test_2025_12_10_lidov3.py | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index 87f9cb169..5abd2f2c1 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -963,6 +963,8 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): def test_register_groups_in_operator_grid(easy_track, trusted_address, stranger): operator_grid = interface.OperatorGrid(OPERATOR_GRID) + chain.snapshot() + operator_addresses = [ "0x0000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000002", @@ -1004,10 +1006,14 @@ def test_register_groups_in_operator_grid(easy_track, trusted_address, stranger) assert tier[6] == tiers_params_array[i][j][4] # liquidityFeeBP assert tier[7] == tiers_params_array[i][j][5] # reservationFeeBP + chain.revert() + def test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger): operator_grid = interface.OperatorGrid(OPERATOR_GRID) + chain.snapshot() + # Define operator addresses operator_addresses = [ "0x0000000000000000000000000000000000000003", @@ -1058,10 +1064,14 @@ def test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger): assert tier[6] == tiers_params_array[i][j][4] # liquidityFeeBP assert tier[7] == tiers_params_array[i][j][5] # reservationFeeBP + chain.revert() + def test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger): operator_grid = interface.OperatorGrid(OPERATOR_GRID) + chain.snapshot() + # Define new tier parameters # (shareLimit, reserveRatioBP, forcedRebalanceThresholdBP, infraFeeBP, liquidityFeeBP, reservationFeeBP) new_tier_params = [(2000, 300, 150, 75, 60, 20), (3000, 400, 200, 100, 80, 30)] @@ -1100,10 +1110,14 @@ def test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger): assert tier[6] == new_tier_params[i][4] # liquidityFeeBP assert tier[7] == new_tier_params[i][5] # reservationFeeBP + chain.revert() + def test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger): operator_grid = interface.OperatorGrid(OPERATOR_GRID) + chain.snapshot() + operator_addresses = ["0x0000000000000000000000000000000000000006", "0x0000000000000000000000000000000000000007"] new_share_limits = [2000, 3000] @@ -1131,10 +1145,14 @@ def test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, assert group[0] == operator_address # operator assert group[1] == new_share_limits[i] # shareLimit + chain.revert() + def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger): operator_grid = interface.OperatorGrid(OPERATOR_GRID) + chain.snapshot() + # First create the vaults vaults = [] for i in range(2): @@ -1163,10 +1181,13 @@ def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger) is_in_jail = operator_grid.isVaultInJail(vault) assert is_in_jail == True + chain.revert() def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger): vault_hub = interface.VaultHub(VAULT_HUB) + chain.snapshot() + # First create the vault creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( stranger, @@ -1240,9 +1261,14 @@ def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, strang assert connection[7] == 1 # liquidityFeeBP assert connection[8] == 0 # reservationFeeBP + chain.revert() + def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger): vault_hub = interface.VaultHub(VAULT_HUB) + + chain.snapshot() + # top up VAULTS_ADAPTER stranger.transfer(VAULTS_ADAPTER, 2 * 10**18) @@ -1316,6 +1342,8 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange assert event["pubkeys"] == "0x" + pubkey.hex() assert event["refundRecipient"] == VAULTS_ADAPTER + chain.revert() + def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): vault_hub = interface.VaultHub(VAULT_HUB) From ed30f8a86ad4c46010076633443d10189839ad17 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 9 Dec 2025 11:59:42 +0300 Subject: [PATCH 143/178] add socialize_bad_debt test --- tests/utils_test_2025_12_10_lidov3.py | 129 ++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index 5abd2f2c1..fb1d49dfb 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -1183,6 +1183,7 @@ def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger) chain.revert() + def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger): vault_hub = interface.VaultHub(VAULT_HUB) @@ -1347,3 +1348,131 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): vault_hub = interface.VaultHub(VAULT_HUB) + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + + chain.snapshot() + + # Enable minting in default group + executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) + operator_grid.alterTiers([0], [(100_000 * 10**18, 300, 250, 50, 40, 10)], {"from": executor}) + + # First create the vaults + creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": "2 ether"}, + ) + bad_debt_vault = creation_tx.events["VaultCreated"][0]["vault"] + + # Fresh report for bad debt vault + current_time = chain.time() + accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) + interface.LazyOracle(LAZY_ORACLE).updateReportData(current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) + lazy_oracle = accounts.at(LAZY_ORACLE, force=True) + vault_hub.applyVaultReport( + bad_debt_vault, + current_time, + 2 * 10**18, + 2 * 10**18, + 0, + 0, + 0, + 0, + {"from": lazy_oracle}) + + bad_debt_dashboard = accounts.at(creation_tx.events["DashboardCreated"][0]["dashboard"], force=True) + vault_hub.mintShares(bad_debt_vault, stranger, 5 * 10**17, {"from": bad_debt_dashboard}) + + creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": "2 ether"}, + ) + vault_acceptor = creation_tx.events["VaultCreated"][0]["vault"] + + max_shares_to_socialize = 1 * 10**16 + + calldata = _encode_calldata(["address[]", "address[]", "uint256[]"], [[bad_debt_vault], [vault_acceptor], [max_shares_to_socialize]]) + + motions_before = easy_track.getMotions() + tx = easy_track.createMotion(SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY, calldata, {"from": trusted_address}) + motions = easy_track.getMotions() + assert len(motions) == len(motions_before) + 1 + + ( + motion_id, + _, + _, + motion_duration, + motion_start_date, + _, + _, + _, + _, + ) = motions[-1] + + chain.mine(1, motion_start_date + motion_duration + 1) + + # Bring fresh report for vaults + current_time = chain.time() + interface.LazyOracle(LAZY_ORACLE).updateReportData(current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) + + # Fresh report for acceptor vault + vault_hub.applyVaultReport( + vault_acceptor, + current_time, + 2 * 10**18, + 2 * 10**18, + 0, + 0, + 0, + 0, + {"from": lazy_oracle}) + + # Make bad debt on second vault + vault_hub.applyVaultReport( + bad_debt_vault, + current_time, + 1 * 10**17, + 2 * 10**18, + 0, + 2 * 10**18, + 0, + 0, + {"from": lazy_oracle}) + + bad_debt_record_before = vault_hub.vaultRecord(bad_debt_vault) + bad_liability_before = bad_debt_record_before[2] + acceptor_record_before = vault_hub.vaultRecord(vault_acceptor) + acceptor_liability_before = acceptor_record_before[2] + + tx = easy_track.enactMotion( + motion_id, + tx.events["MotionCreated"]["_evmScriptCallData"], + {"from": stranger}, + ) + + bad_debt_record_after = vault_hub.vaultRecord(bad_debt_vault) + bad_liability_after = bad_debt_record_after[2] + acceptor_record_after = vault_hub.vaultRecord(vault_acceptor) + acceptor_liability_after = acceptor_record_after[2] + + assert bad_liability_after == bad_liability_before - max_shares_to_socialize + assert acceptor_liability_after == acceptor_liability_before + max_shares_to_socialize + + # Check that events were emitted for failed socializations + assert len(tx.events["BadDebtSocialized"]) == 1 + event = tx.events["BadDebtSocialized"][0] + assert event["vaultDonor"] == bad_debt_vault + assert event["vaultAcceptor"] == vault_acceptor + assert event["badDebtShares"] == max_shares_to_socialize + + chain.revert() From fb0e95f47d0972d9a434a68447f7e86c012d0a15 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Tue, 9 Dec 2025 09:14:16 +0000 Subject: [PATCH 144/178] fix: TRP ET limit tests --- tests/utils_test_2025_12_10_operations.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py index a93fba9aa..49692375c 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_10_operations.py @@ -121,7 +121,6 @@ class TokenLimit(NamedTuple): MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 TRP_LIMIT_BEFORE = 9_178_284_420 * 10**15 # == 9_178_284.42 * 10**18 -TRP_ALREADY_SPENT_AFTER = 4208709 * 10**18 TRP_LIMIT_AFTER = 15_000_000 * 10**18 TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC @@ -778,6 +777,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): curated_module_before = None sdvt_module_before = None a41_summary_before = None + TRP_ALREADY_SPENT_AFTER = None if EXPECTED_DG_PROPOSAL_ID is not None: details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) @@ -820,6 +820,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): assert trp_limit_before == TRP_LIMIT_BEFORE assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - trp_already_spent_amount_before + TRP_ALREADY_SPENT_AFTER = trp_already_spent_amount_before # TRP_ALREADY_SPENT_AFTER must be the same as before the execution assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP @@ -953,8 +954,9 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() assert trp_limit_after == TRP_LIMIT_AFTER assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS - assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER - assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + if TRP_ALREADY_SPENT_AFTER is not None: + assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER + assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP From fc06b337d88150ff7d8e9b917fb4068e03b3d522 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 9 Dec 2025 15:23:57 +0300 Subject: [PATCH 145/178] test: optional v3 et tests --- tests/test_2025_12_10.py | 2 +- tests/utils_test_2025_12_10_lidov3.py | 144 ++++++++++---------------- 2 files changed, 57 insertions(+), 89 deletions(-) diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index c03ac68bb..f084dc795 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -17,7 +17,7 @@ def test_vote_v1_v2_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, st 195, 7, ) - lidov3.enact_and_test_dg(stranger, 6) + lidov3.enact_and_test_dg(stranger, 6, True) ops.enact_and_test_dg(stranger, 7) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index fb1d49dfb..99e3aea7c 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -540,7 +540,7 @@ def enact_and_test_voting( event=vote_events[1], p=EVMScriptFactoryAdded( factory_addr=ALTER_TIERS_IN_OPERATOR_GRID_FACTORY, - permissions=create_permissions(interface.OperatorGrid(OPERATOR_GRID), "alterTiers") + permissions=create_permissions(operator_grid, "alterTiers") ), emitted_by=easy_track, ) @@ -549,7 +549,7 @@ def enact_and_test_voting( event=vote_events[2], p=EVMScriptFactoryAdded( factory_addr=REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY, - permissions=create_permissions(interface.OperatorGrid(OPERATOR_GRID), "registerGroup") + create_permissions(interface.OperatorGrid(OPERATOR_GRID), "registerTiers")[2:] + permissions=create_permissions(operator_grid, "registerGroup") + create_permissions(operator_grid, "registerTiers")[2:] ), emitted_by=easy_track, ) @@ -558,7 +558,7 @@ def enact_and_test_voting( event=vote_events[3], p=EVMScriptFactoryAdded( factory_addr=REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY, - permissions=create_permissions(interface.OperatorGrid(OPERATOR_GRID), "registerTiers") + permissions=create_permissions(operator_grid, "registerTiers") ), emitted_by=easy_track, ) @@ -567,16 +567,17 @@ def enact_and_test_voting( event=vote_events[4], p=EVMScriptFactoryAdded( factory_addr=UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY, - permissions=create_permissions(interface.OperatorGrid(OPERATOR_GRID), "updateGroupShareLimit") + permissions=create_permissions(operator_grid, "updateGroupShareLimit") ), emitted_by=easy_track, ) + vaults_adapter = interface.IVaultsAdapter(VAULTS_ADAPTER) validate_evmscript_factory_added_event( event=vote_events[5], p=EVMScriptFactoryAdded( factory_addr=SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY, - permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "setVaultJailStatus") + permissions=create_permissions(vaults_adapter, "setVaultJailStatus") ), emitted_by=easy_track, ) @@ -585,7 +586,7 @@ def enact_and_test_voting( event=vote_events[6], p=EVMScriptFactoryAdded( factory_addr=UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY, - permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "updateVaultFees") + permissions=create_permissions(vaults_adapter, "updateVaultFees") ), emitted_by=easy_track, ) @@ -594,7 +595,7 @@ def enact_and_test_voting( event=vote_events[7], p=EVMScriptFactoryAdded( factory_addr=FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY, - permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "forceValidatorExit") + permissions=create_permissions(vaults_adapter, "forceValidatorExit") ), emitted_by=easy_track, ) @@ -603,13 +604,13 @@ def enact_and_test_voting( event=vote_events[8], p=EVMScriptFactoryAdded( factory_addr=SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY, - permissions=create_permissions(interface.IVaultsAdapter(VAULTS_ADAPTER), "socializeBadDebt") + permissions=create_permissions(vaults_adapter, "socializeBadDebt") ), emitted_by=easy_track, ) -def enact_and_test_dg(stranger, expected_dg_proposal_id): +def enact_and_test_dg(stranger, expected_dg_proposal_id, run_et_tests=False): """ Enact and test the dual governance proposal. Includes all before/after DG checks and event validation. @@ -637,6 +638,11 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): upgradeTemplate = interface.UpgradeTemplateV3(UPGRADE_TEMPLATE) lido = interface.Lido(LIDO) + vault_hub = interface.VaultHub(VAULT_HUB) + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + lazy_oracle = interface.LazyOracle(LAZY_ORACLE) + vault_factory = interface.VaultFactory(VAULTS_FACTORY) + # Save original implementations for comparison locator_impl_before = get_ossifiable_proxy_impl(LIDO_LOCATOR) accounting_oracle_impl_before = get_ossifiable_proxy_impl(ACCOUNTING_ORACLE) @@ -935,7 +941,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): upgradeTemplate.finishUpgrade({"from": agent_account}) # Check that after the DG proposal has passed, creation of the vaults via VaultFactory can be done - creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + creation_tx = vault_factory.createVaultWithDashboard( stranger, stranger, stranger, @@ -948,22 +954,22 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert creation_tx.events.count("DashboardCreated") == 1 # Scenario tests for Easy Track factories behavior after the vote - trusted_address = accounts.at(ST_VAULTS_COMMITTEE, force=True) - easy_track = interface.EasyTrack(EASYTRACK) - test_register_groups_in_operator_grid(easy_track, trusted_address, stranger) - test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger) - test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger) - test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger) - test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger) - test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger) - test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger) - test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger) - - -def test_register_groups_in_operator_grid(easy_track, trusted_address, stranger): - operator_grid = interface.OperatorGrid(OPERATOR_GRID) - - chain.snapshot() + if run_et_tests: + trusted_address = accounts.at(ST_VAULTS_COMMITTEE, force=True) + easy_track = interface.EasyTrack(EASYTRACK) + chain.snapshot() + test_register_groups_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger, operator_grid, vault_factory) + test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory) + test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory) + test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, operator_grid, lazy_oracle, vault_hub, vault_factory) + chain.revert() + + +def test_register_groups_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): operator_addresses = [ "0x0000000000000000000000000000000000000001", @@ -1006,13 +1012,8 @@ def test_register_groups_in_operator_grid(easy_track, trusted_address, stranger) assert tier[6] == tiers_params_array[i][j][4] # liquidityFeeBP assert tier[7] == tiers_params_array[i][j][5] # reservationFeeBP - chain.revert() - -def test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger): - operator_grid = interface.OperatorGrid(OPERATOR_GRID) - - chain.snapshot() +def test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): # Define operator addresses operator_addresses = [ @@ -1064,13 +1065,8 @@ def test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger): assert tier[6] == tiers_params_array[i][j][4] # liquidityFeeBP assert tier[7] == tiers_params_array[i][j][5] # reservationFeeBP - chain.revert() - -def test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger): - operator_grid = interface.OperatorGrid(OPERATOR_GRID) - - chain.snapshot() +def test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): # Define new tier parameters # (shareLimit, reserveRatioBP, forcedRebalanceThresholdBP, infraFeeBP, liquidityFeeBP, reservationFeeBP) @@ -1110,13 +1106,8 @@ def test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger): assert tier[6] == new_tier_params[i][4] # liquidityFeeBP assert tier[7] == new_tier_params[i][5] # reservationFeeBP - chain.revert() - -def test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger): - operator_grid = interface.OperatorGrid(OPERATOR_GRID) - - chain.snapshot() +def test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): operator_addresses = ["0x0000000000000000000000000000000000000006", "0x0000000000000000000000000000000000000007"] new_share_limits = [2000, 3000] @@ -1145,18 +1136,13 @@ def test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, assert group[0] == operator_address # operator assert group[1] == new_share_limits[i] # shareLimit - chain.revert() - - -def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger): - operator_grid = interface.OperatorGrid(OPERATOR_GRID) - chain.snapshot() +def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger, operator_grid, vault_factory): # First create the vaults vaults = [] for i in range(2): - creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + creation_tx = vault_factory.createVaultWithDashboard( stranger, stranger, stranger, @@ -1181,16 +1167,11 @@ def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger) is_in_jail = operator_grid.isVaultInJail(vault) assert is_in_jail == True - chain.revert() - -def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger): - vault_hub = interface.VaultHub(VAULT_HUB) - - chain.snapshot() +def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory): # First create the vault - creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + creation_tx = vault_factory.createVaultWithDashboard( stranger, stranger, stranger, @@ -1231,14 +1212,14 @@ def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, strang # bring fresh report for vault current_time = chain.time() accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) - interface.LazyOracle(LAZY_ORACLE).updateReportData( + lazy_oracle.updateReportData( current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) - lazy_oracle = accounts.at(LAZY_ORACLE, force=True) + lazy_oracle_account = accounts.at(LAZY_ORACLE, force=True) vault_hub.applyVaultReport( vault, current_time, @@ -1248,7 +1229,7 @@ def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, strang 0, 0, 0, - {"from": lazy_oracle}) + {"from": lazy_oracle_account}) easy_track.enactMotion( motion_id, @@ -1262,20 +1243,15 @@ def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, strang assert connection[7] == 1 # liquidityFeeBP assert connection[8] == 0 # reservationFeeBP - chain.revert() - -def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger): - vault_hub = interface.VaultHub(VAULT_HUB) - - chain.snapshot() +def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory): # top up VAULTS_ADAPTER stranger.transfer(VAULTS_ADAPTER, 2 * 10**18) pubkey = b"01" * 48 # First create the vault - creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + creation_tx = vault_factory.createVaultWithDashboard( stranger, stranger, stranger, @@ -1310,7 +1286,7 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange # bring fresh report for vault current_time = chain.time() accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) - interface.LazyOracle(LAZY_ORACLE).updateReportData( + lazy_oracle.updateReportData( current_time, 1000, "0x00", @@ -1318,7 +1294,7 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange {"from": accounting_oracle}) # make vault unhealthy - lazy_oracle = accounts.at(LAZY_ORACLE, force=True) + lazy_oracle_account = accounts.at(LAZY_ORACLE, force=True) vault_hub.applyVaultReport( vault, current_time, @@ -1328,7 +1304,7 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange 0, 0, 0, - {"from": lazy_oracle}) + {"from": lazy_oracle_account}) tx = easy_track.enactMotion( motion_id, @@ -1343,21 +1319,15 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange assert event["pubkeys"] == "0x" + pubkey.hex() assert event["refundRecipient"] == VAULTS_ADAPTER - chain.revert() - -def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): - vault_hub = interface.VaultHub(VAULT_HUB) - operator_grid = interface.OperatorGrid(OPERATOR_GRID) - - chain.snapshot() +def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, operator_grid, lazy_oracle, vault_hub, vault_factory): # Enable minting in default group executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) operator_grid.alterTiers([0], [(100_000 * 10**18, 300, 250, 50, 40, 10)], {"from": executor}) # First create the vaults - creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + creation_tx = vault_factory.createVaultWithDashboard( stranger, stranger, stranger, @@ -1371,8 +1341,8 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): # Fresh report for bad debt vault current_time = chain.time() accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) - interface.LazyOracle(LAZY_ORACLE).updateReportData(current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) - lazy_oracle = accounts.at(LAZY_ORACLE, force=True) + lazy_oracle.updateReportData(current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) + lazy_oracle_account = accounts.at(LAZY_ORACLE, force=True) vault_hub.applyVaultReport( bad_debt_vault, current_time, @@ -1382,12 +1352,12 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): 0, 0, 0, - {"from": lazy_oracle}) + {"from": lazy_oracle_account}) bad_debt_dashboard = accounts.at(creation_tx.events["DashboardCreated"][0]["dashboard"], force=True) vault_hub.mintShares(bad_debt_vault, stranger, 5 * 10**17, {"from": bad_debt_dashboard}) - creation_tx = interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + creation_tx = vault_factory.createVaultWithDashboard( stranger, stranger, stranger, @@ -1423,7 +1393,7 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): # Bring fresh report for vaults current_time = chain.time() - interface.LazyOracle(LAZY_ORACLE).updateReportData(current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) + lazy_oracle.updateReportData(current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) # Fresh report for acceptor vault vault_hub.applyVaultReport( @@ -1435,7 +1405,7 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): 0, 0, 0, - {"from": lazy_oracle}) + {"from": lazy_oracle_account}) # Make bad debt on second vault vault_hub.applyVaultReport( @@ -1447,7 +1417,7 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): 2 * 10**18, 0, 0, - {"from": lazy_oracle}) + {"from": lazy_oracle_account}) bad_debt_record_before = vault_hub.vaultRecord(bad_debt_vault) bad_liability_before = bad_debt_record_before[2] @@ -1474,5 +1444,3 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger): assert event["vaultDonor"] == bad_debt_vault assert event["vaultAcceptor"] == vault_acceptor assert event["badDebtShares"] == max_shares_to_socialize - - chain.revert() From 2e4153f6986b2f0862639bd40c25fa2ee83af1d4 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 9 Dec 2025 15:40:16 +0300 Subject: [PATCH 146/178] feat: autofetch_sources: false --- brownie-config.yml | 2 +- tests/test_2025_12_10.py | 2 +- tests/utils_test_2025_12_10_lidov3.py | 27 +++++++++++++-------------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/brownie-config.yml b/brownie-config.yml index 98db6cfbe..09ffaa9d3 100644 --- a/brownie-config.yml +++ b/brownie-config.yml @@ -9,7 +9,7 @@ networks: live: priority_fee: auto -autofetch_sources: true +autofetch_sources: false hypothesis: max_examples: 10 diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index f084dc795..c03ac68bb 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -17,7 +17,7 @@ def test_vote_v1_v2_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, st 195, 7, ) - lidov3.enact_and_test_dg(stranger, 6, True) + lidov3.enact_and_test_dg(stranger, 6) ops.enact_and_test_dg(stranger, 7) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index 99e3aea7c..dc92db4bc 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -610,7 +610,7 @@ def enact_and_test_voting( ) -def enact_and_test_dg(stranger, expected_dg_proposal_id, run_et_tests=False): +def enact_and_test_dg(stranger, expected_dg_proposal_id): """ Enact and test the dual governance proposal. Includes all before/after DG checks and event validation. @@ -954,19 +954,18 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id, run_et_tests=False): assert creation_tx.events.count("DashboardCreated") == 1 # Scenario tests for Easy Track factories behavior after the vote - if run_et_tests: - trusted_address = accounts.at(ST_VAULTS_COMMITTEE, force=True) - easy_track = interface.EasyTrack(EASYTRACK) - chain.snapshot() - test_register_groups_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) - test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) - test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) - test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) - test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger, operator_grid, vault_factory) - test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory) - test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory) - test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, operator_grid, lazy_oracle, vault_hub, vault_factory) - chain.revert() + trusted_address = accounts.at(ST_VAULTS_COMMITTEE, force=True) + easy_track = interface.EasyTrack(EASYTRACK) + chain.snapshot() + test_register_groups_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger, operator_grid, vault_factory) + test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory) + test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory) + test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, operator_grid, lazy_oracle, vault_hub, vault_factory) + chain.revert() def test_register_groups_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): From f84b0b20dcaa8c04929bbc7bad289f685619d868 Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 9 Dec 2025 16:33:45 +0300 Subject: [PATCH 147/178] test: fix snap tests --- tests/snapshot/test_lido_snapshot.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/snapshot/test_lido_snapshot.py b/tests/snapshot/test_lido_snapshot.py index 14149f93f..9461342ce 100644 --- a/tests/snapshot/test_lido_snapshot.py +++ b/tests/snapshot/test_lido_snapshot.py @@ -170,7 +170,8 @@ def test_lido_send_ether_snapshot( assert lido.balanceOf(eth_whale) == 0 assert eth_whale.balance() >= _1ETH - assert el_vault.balance() >= _1ETH + if el_vault.balance() < _1ETH: + eth_whale.transfer(el_vault.address, _1ETH) def get_actions(from_address: Account | None = None): return ( @@ -231,13 +232,14 @@ def get_actions(from_address: Account | None = None): _stacks_equal(stacks) -def test_lido_dao_ops_snapshot(sandwich_upgrade: SandwichFn): +def test_lido_dao_ops_snapshot(sandwich_upgrade: SandwichFn, eth_whale: Account): el_vault = contracts.execution_layer_rewards_vault lido = contracts.lido assert lido.getCurrentStakeLimit() > 0 assert lido.isStakingPaused() is False - assert el_vault.balance() >= _1ETH + if el_vault.balance() < _1ETH: + eth_whale.transfer(el_vault.address, _1ETH) assert lido.isStopped() is False def get_actions(from_address: Account | None = None): From e74f0236cc6a533285d7f439ea9fde4cfeb40c1d Mon Sep 17 00:00:00 2001 From: dry914 Date: Tue, 9 Dec 2025 16:48:26 +0300 Subject: [PATCH 148/178] test: fix set_balance_in_wei --- utils/balance.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/utils/balance.py b/utils/balance.py index 2ed745c26..dbcb160ec 100644 --- a/utils/balance.py +++ b/utils/balance.py @@ -3,7 +3,10 @@ def set_balance_in_wei(address, balance): - account = accounts.at(address, force=True) + # Accept both a plain address string and a Brownie Account object + address_str = address.address if hasattr(address, "address") else address + + account = accounts.at(address_str, force=True) providers = ["evm_setAccountBalance", "hardhat_setBalance", "anvil_setBalance"] for provider in providers: @@ -11,12 +14,12 @@ def set_balance_in_wei(address, balance): break try: - web3.provider.make_request(provider, [address, hex(balance)]) + web3.provider.make_request(provider, [address_str, hex(balance)]) except ValueError as e: if e.args[0].get("message") != f"Method {provider} is not supported": raise e - assert account.balance() == balance, f"Failed to set balance {balance} for account: {address}" + assert account.balance() == balance, f"Failed to set balance {balance} for account: {address_str}" return account From 2f283e297104537eee4f11664f69db6f236c3854 Mon Sep 17 00:00:00 2001 From: dry914 Date: Wed, 10 Dec 2025 13:37:49 +0300 Subject: [PATCH 149/178] feat: pdg paused --- scripts/upgrade_2025_12_10_mainnet_v3.py | 2 +- tests/regression/test_gate_seal.py | 5 ++ tests/utils_test_2025_12_10_lidov3.py | 82 ++++++++++++++++++++++-- utils/test/event_validators/unpause.py | 14 ++++ 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index ff821818d..8690fa0b6 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -45,7 +45,7 @@ # ============================== Addresses =================================== -OMNIBUS_CONTRACT = "0xf2BeCc7aF0AA50DaD54781e06d5ce1A7eAD59AfA" +OMNIBUS_CONTRACT = "0xE1F4c16908fCE6935b5Ad38C6e3d58830fe86442" # ============================= Description ================================== diff --git a/tests/regression/test_gate_seal.py b/tests/regression/test_gate_seal.py index 4ded3162d..98b07daba 100644 --- a/tests/regression/test_gate_seal.py +++ b/tests/regression/test_gate_seal.py @@ -27,6 +27,7 @@ VAULT_HUB, PREDEPOSIT_GUARANTEE, GATE_SEAL_V3, + RESEAL_MANAGER, ) @@ -356,6 +357,10 @@ def test_gate_seal_v3_vaults_scenario(gate_seal_committee): pause_duration = gate_seal_v3.get_seal_duration_seconds() + # TODO remove this after PDG unpause + reseal_manager_account = accounts.at(RESEAL_MANAGER, force=True) + contracts.predeposit_guarantee.resume({"from": reseal_manager_account}) + assert not contracts.vault_hub.isPaused() assert not contracts.predeposit_guarantee.isPaused() diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index dc92db4bc..80cc4ef70 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -23,6 +23,7 @@ from utils.test.event_validators.permission import validate_grant_role_event, validate_revoke_role_event from utils.test.event_validators.aragon import validate_aragon_set_app_event, validate_aragon_grant_permission_event, validate_aragon_revoke_permission_event from utils.test.easy_track_helpers import _encode_calldata, create_and_enact_motion +from utils.test.event_validators.unpause import validate_pause_for_event # ============================================================================ @@ -103,6 +104,7 @@ SLASHING_RESERVE_SHIFT = 8192 MAX_EXTERNAL_RATIO_BP = 300 # 3% INFINITE_ALLOWANCE = 2**256 - 1 # type(uint256).max +PAUSE_INFINITELY = 2**256 - 1 # type(uint256).max # ============================================================================ @@ -255,6 +257,7 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): kernel = interface.Kernel(ARAGON_KERNEL) acl = interface.ACL(ACL) staking_router = interface.StakingRouter(STAKING_ROUTER) + predeposit_guarantee = interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) dg_items = [ # 1.1. Ensure DG proposal execution is within daily time window (14:00 UTC - 23:00 UTC) @@ -395,7 +398,32 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): ) ]), - # 1.18. Call V3Template.finishUpgrade + # 1.18. Grant PredepositGuarantee's PAUSE_ROLE to Agent + agent_forward([ + encode_oz_grant_role( + contract=predeposit_guarantee, + role_name="PausableUntilWithRoles.PauseRole", + grant_to=AGENT + ) + ]), + + # 1.19. Pause PredepositGuarantee + agent_forward([ + (predeposit_guarantee.address, predeposit_guarantee.pauseFor.encode_input( + web3.codec.encode(['uint256'], [PAUSE_INFINITELY]) + )) + ]), + + # 1.20. Revoke PredepositGuarantee's PAUSE_ROLE from Agent + agent_forward([ + encode_oz_revoke_role( + contract=predeposit_guarantee, + role_name="PausableUntilWithRoles.PauseRole", + revoke_from=AGENT + ) + ]), + + # 1.21. Call V3Template.finishUpgrade agent_forward([ (upgradeTemplate.address, upgradeTemplate.finishUpgrade.encode_input()) ]), @@ -618,8 +646,8 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): if expected_dg_proposal_id is None: return - EXPECTED_DG_EVENTS_FROM_AGENT = 17 - EXPECTED_DG_EVENTS_COUNT = 18 + EXPECTED_DG_EVENTS_FROM_AGENT = 20 + EXPECTED_DG_EVENTS_COUNT = 21 # ======================================================================= # ========================= Arrange variables =========================== @@ -642,6 +670,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): operator_grid = interface.OperatorGrid(OPERATOR_GRID) lazy_oracle = interface.LazyOracle(LAZY_ORACLE) vault_factory = interface.VaultFactory(VAULTS_FACTORY) + predeposit_guarantee = interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) # Save original implementations for comparison locator_impl_before = get_ossifiable_proxy_impl(LIDO_LOCATOR) @@ -651,6 +680,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") config_manager_role = web3.keccak(text="CONFIG_MANAGER_ROLE") app_manager_role = web3.keccak(text="APP_MANAGER_ROLE") + pdg_pause_role = web3.keccak(text="PausableUntilWithRoles.PauseRole") details = timelock.getProposalDetails(expected_dg_proposal_id) if details["status"] != PROPOSAL_STATUS["executed"]: @@ -713,7 +743,13 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): except Exception: pass # Expected to fail - # Step 1.18. Call V3Template.finishUpgrade + # Step 1.18. Grant PredepositGuarantee's PAUSE_ROLE to Agent + assert not predeposit_guarantee.hasRole(pdg_pause_role, AGENT), "PredepositGuarantee should not have PAUSE_ROLE on Agent before upgrade" + + # Step 1.19. Pause PredepositGuarantee + assert predeposit_guarantee.isPaused() == False, "PredepositGuarantee should not be paused before upgrade" + + # Step 1.21. Call V3Template.finishUpgrade assert lido.getContractVersion() == NEW_LIDO_VERSION - 1, "LIDO should have version 2 before finishUpgrade" assert lido.allowance(CSM_ACCOUNTING, BURNER) == 0, "No allowance from CSM_ACCOUNTING to BURNER before finishUpgrade" assert lido.allowance(CSM_ACCOUNTING, OLD_BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from CSM_ACCOUNTING to OLD_BURNER before finishUpgrade" @@ -870,8 +906,34 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): emitted_by=oracle_daemon_config, ) - # 1.18. Call V3Template.finishUpgrade - validate_upgrade_finished_events(dg_events[17]) + # 1.18. Grant PredepositGuarantee's PAUSE_ROLE to Agent + validate_grant_role_event( + dg_events[17], + role=pdg_pause_role.hex(), + grant_to=AGENT, + sender=AGENT, + emitted_by=predeposit_guarantee, + ) + + # 1.19. Pause PredepositGuarantee + validate_pause_for_event( + dg_events[18], + pause_for=PAUSE_INFINITELY, + sender=AGENT, + emitted_by=predeposit_guarantee, + ) + + # 1.20. Revoke PredepositGuarantee's PAUSE_ROLE from Agent + validate_revoke_role_event( + dg_events[19], + role=pdg_pause_role.hex(), + revoke_from=AGENT, + sender=AGENT, + emitted_by=predeposit_guarantee, + ) + + # 1.21. Call V3Template.finishUpgrade + validate_upgrade_finished_events(dg_events[20]) # ========================================================================= # ==================== After DG proposal executed checks ================== @@ -921,7 +983,13 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # Step 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent after upgrade" - # Step 1.18. Call V3Template.finishUpgrade + # Step 1.19. Pause PredepositGuarantee + assert predeposit_guarantee.isPaused() == True, "PredepositGuarantee should be paused after upgrade" + + # Step 1.20. Revoke PredepositGuarantee's PAUSE_ROLE from Agent + assert not predeposit_guarantee.hasRole(pdg_pause_role, AGENT), "PredepositGuarantee should not have PAUSE_ROLE on Agent after upgrade" + + # Step 1.21. Call V3Template.finishUpgrade lido = interface.Lido(LIDO) assert lido.getContractVersion() == NEW_LIDO_VERSION, "LIDO should have version 3 after finishUpgrade" assert lido.getMaxExternalRatioBP() == MAX_EXTERNAL_RATIO_BP, "LIDO should have max external ratio 3% after finishUpgrade" diff --git a/utils/test/event_validators/unpause.py b/utils/test/event_validators/unpause.py index 10eb5e73a..218a3bb39 100644 --- a/utils/test/event_validators/unpause.py +++ b/utils/test/event_validators/unpause.py @@ -3,6 +3,7 @@ from typing import NamedTuple from brownie.network.event import EventDict +from brownie import convert from .common import validate_events_chain @@ -13,3 +14,16 @@ def validate_unpause_event(event: EventDict): validate_events_chain([e.name for e in event], _unpause_events_chain) assert event.count('DepositsUnpaused') == 1 + + +def validate_pause_for_event(events: EventDict, pause_for: int, sender: str, emitted_by: str): + # extra 'LogScriptCall' and 'ScriptResult' due to agent forwarding + _events_chain = ['LogScriptCall', 'LogScriptCall', 'Paused', 'ScriptResult', 'Executed'] + + validate_events_chain([e.name for e in events], _events_chain) + + assert events.count("Paused") == 1 + + assert events["Paused"]["duration"] == pause_for, "Wrong duration" + + assert convert.to_address(events["Paused"]["_emitted_by"]) == convert.to_address(emitted_by), "Wrong event emitter" From d6c21be649c809c7496e08551625d02e1eb3f7ca Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 11:06:37 +0000 Subject: [PATCH 150/178] fix: download solc via CI --- .github/actions/brownie_fork_tests/action.yml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 154eb024e..473fe1cff 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -69,6 +69,34 @@ runs: echo "Vote type" echo ${{ inputs.vote }} + - name: Install solc + shell: bash + run: | + set -e + + mkdir -p /home/runner/.solcx + + echo "Downloading solc..." + curl -L "https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.6.11+commit.5ef660b1" -o /home/runner/.solcx/solc-v0.8.28 + + chmod +x /home/runner/.solcx/solc-v0.8.28 + + echo "Checking solc version..." + output="$( /home/runner/.solcx/solc-v0.8.28 --version )" + echo "$output" + + expected="Version: 0.6.11+commit.5ef660b1" + + if [[ "$output" != *"$expected"* ]]; then + echo "❌ solc version mismatch!" + echo "Expected: $expected" + echo "Got: $output" + exit 1 + fi + + echo "✔ solc version is correct" + + - name: Run tests shell: bash run: > From 1671279eeb0b8181bb700e17bc611dad17904f2f Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 11:17:16 +0000 Subject: [PATCH 151/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 40 +------------ install-solc.sh | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 39 deletions(-) create mode 100644 install-solc.sh diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 473fe1cff..277f37fe3 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -54,48 +54,10 @@ runs: shell: bash run: poetry run brownie networks import network-config.yaml True - - name: Show system info - shell: bash - run: | - echo "Memory and swap:" - free -h - echo - swapon --show - echo - df -h - echo - echo "CPU units" - nproc --all - echo "Vote type" - echo ${{ inputs.vote }} - - name: Install solc shell: bash run: | - set -e - - mkdir -p /home/runner/.solcx - - echo "Downloading solc..." - curl -L "https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.6.11+commit.5ef660b1" -o /home/runner/.solcx/solc-v0.8.28 - - chmod +x /home/runner/.solcx/solc-v0.8.28 - - echo "Checking solc version..." - output="$( /home/runner/.solcx/solc-v0.8.28 --version )" - echo "$output" - - expected="Version: 0.6.11+commit.5ef660b1" - - if [[ "$output" != *"$expected"* ]]; then - echo "❌ solc version mismatch!" - echo "Expected: $expected" - echo "Got: $output" - exit 1 - fi - - echo "✔ solc version is correct" - + install-solc-versions.sh - name: Run tests shell: bash diff --git a/install-solc.sh b/install-solc.sh new file mode 100644 index 000000000..a33fdd81b --- /dev/null +++ b/install-solc.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -euo pipefail + +SOLC_DIR="${HOME}/.solcx" + +mkdir -p "${SOLC_DIR}" + +SOLC_VERSIONS=( + "0.4.24+commit.e67f0147" + "0.5.14+commit.01f1aaa4" + "0.5.12+commit.7709ece9" + "0.6.12+commit.27d51765" + "0.8.28+commit.7893614a" + "0.8.26+commit.8a97fa7" + "0.8.10+commit.fc410830" + "0.8.9+commit.e5eed63a" + "0.8.4+commit.c7e474f2" + "0.8.6+commit.11564f7e" + "0.7.6+commit.7338295f" + "0.8.15+commit.e14f2714" + "0.8.19+commit.7dd6d404" + "0.8.24+commit.e11b9ed9" + "0.8.25+commit.b61c2a91" + "0.8.21+commit.d9974bed" + "0.6.11+commit.5ef660b1" +) + +for version in "${SOLC_VERSIONS[@]}"; do + echo "========================================" + echo "Downloading solc version: ${version}" + + # Example final URL: + # https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.6.11+commit.5ef660b1 + url="https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v${version}" + bin_path="${SOLC_DIR}/solc-v${version}" + + echo "URL: ${url}" + echo "Target: ${bin_path}" + + curl -L "${url}" -o "${bin_path}" + + chmod +x "${bin_path}" + + echo "Checking solc version for ${version}..." + output="$("${bin_path}" --version || true)" + echo "${output}" + + expected="Version: ${version}" + + if [[ "${output}" != *"${expected}"* ]]; then + echo "❌ solc version mismatch!" + echo "Expected: ${expected}" + echo "Got: ${output}" + exit 1 + fi + + echo "✔ solc version ${version} is correct" +done \ No newline at end of file From 70472c1e868823c35e584b684c3ce4878a63e496 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 11:19:40 +0000 Subject: [PATCH 152/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 277f37fe3..4cd1fdb4d 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -57,7 +57,7 @@ runs: - name: Install solc shell: bash run: | - install-solc-versions.sh + bash ./install-solc-versions.sh - name: Run tests shell: bash From 93fb9e81892e57d0a869401c8f9234d4c541376c Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 11:20:58 +0000 Subject: [PATCH 153/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 4cd1fdb4d..8a73c81d8 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -57,7 +57,7 @@ runs: - name: Install solc shell: bash run: | - bash ./install-solc-versions.sh + bash ./install-solc.sh - name: Run tests shell: bash From 5d25f0dc69a41a66cac110ff873b9e41bb5df36a Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 11:23:11 +0000 Subject: [PATCH 154/178] fix: download solc for CI --- install-solc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install-solc.sh b/install-solc.sh index a33fdd81b..88157c209 100644 --- a/install-solc.sh +++ b/install-solc.sh @@ -11,7 +11,6 @@ SOLC_VERSIONS=( "0.5.12+commit.7709ece9" "0.6.12+commit.27d51765" "0.8.28+commit.7893614a" - "0.8.26+commit.8a97fa7" "0.8.10+commit.fc410830" "0.8.9+commit.e5eed63a" "0.8.4+commit.c7e474f2" @@ -23,6 +22,7 @@ SOLC_VERSIONS=( "0.8.25+commit.b61c2a91" "0.8.21+commit.d9974bed" "0.6.11+commit.5ef660b1" + "0.8.26+commit.8a97fa7a" ) for version in "${SOLC_VERSIONS[@]}"; do From 0f2a093cdb8e4abbf1acefb0d03f74086a7bb24b Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 11:27:29 +0000 Subject: [PATCH 155/178] fix: download solc for CI --- install-solc.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/install-solc.sh b/install-solc.sh index 88157c209..c58bb1c0d 100644 --- a/install-solc.sh +++ b/install-solc.sh @@ -23,6 +23,7 @@ SOLC_VERSIONS=( "0.8.21+commit.d9974bed" "0.6.11+commit.5ef660b1" "0.8.26+commit.8a97fa7a" + "0.8.31+commit.fd3a2265" ) for version in "${SOLC_VERSIONS[@]}"; do From a61de6e9531570e8e0b106502e86d1275814e327 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 11:45:30 +0000 Subject: [PATCH 156/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 5 -- install-solc.sh | 59 ------------------- sitecustomize.py | 9 +++ 3 files changed, 9 insertions(+), 64 deletions(-) delete mode 100644 install-solc.sh create mode 100644 sitecustomize.py diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 8a73c81d8..8572318c6 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -54,11 +54,6 @@ runs: shell: bash run: poetry run brownie networks import network-config.yaml True - - name: Install solc - shell: bash - run: | - bash ./install-solc.sh - - name: Run tests shell: bash run: > diff --git a/install-solc.sh b/install-solc.sh deleted file mode 100644 index c58bb1c0d..000000000 --- a/install-solc.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SOLC_DIR="${HOME}/.solcx" - -mkdir -p "${SOLC_DIR}" - -SOLC_VERSIONS=( - "0.4.24+commit.e67f0147" - "0.5.14+commit.01f1aaa4" - "0.5.12+commit.7709ece9" - "0.6.12+commit.27d51765" - "0.8.28+commit.7893614a" - "0.8.10+commit.fc410830" - "0.8.9+commit.e5eed63a" - "0.8.4+commit.c7e474f2" - "0.8.6+commit.11564f7e" - "0.7.6+commit.7338295f" - "0.8.15+commit.e14f2714" - "0.8.19+commit.7dd6d404" - "0.8.24+commit.e11b9ed9" - "0.8.25+commit.b61c2a91" - "0.8.21+commit.d9974bed" - "0.6.11+commit.5ef660b1" - "0.8.26+commit.8a97fa7a" - "0.8.31+commit.fd3a2265" -) - -for version in "${SOLC_VERSIONS[@]}"; do - echo "========================================" - echo "Downloading solc version: ${version}" - - # Example final URL: - # https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.6.11+commit.5ef660b1 - url="https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v${version}" - bin_path="${SOLC_DIR}/solc-v${version}" - - echo "URL: ${url}" - echo "Target: ${bin_path}" - - curl -L "${url}" -o "${bin_path}" - - chmod +x "${bin_path}" - - echo "Checking solc version for ${version}..." - output="$("${bin_path}" --version || true)" - echo "${output}" - - expected="Version: ${version}" - - if [[ "${output}" != *"${expected}"* ]]; then - echo "❌ solc version mismatch!" - echo "Expected: ${expected}" - echo "Got: ${output}" - exit 1 - fi - - echo "✔ solc version ${version} is correct" -done \ No newline at end of file diff --git a/sitecustomize.py b/sitecustomize.py new file mode 100644 index 000000000..401970b51 --- /dev/null +++ b/sitecustomize.py @@ -0,0 +1,9 @@ +# sitecustomize.py +try: + import solcx.install as solcx_install +except ImportError: + # py-solc-x not installed in this environment; nothing to patch + pass +else: + # change this to your mirror / URL + solcx_install.BINARY_DOWNLOAD_BASE = "https://binaries.soliditylang.org/{}-amd64/{}" From bcd9fedf47a9f47a12c7072aaa5c899cb2e77dec Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:30:18 +0000 Subject: [PATCH 157/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 8572318c6..d2f2f6758 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -54,6 +54,10 @@ runs: shell: bash run: poetry run brownie networks import network-config.yaml True + - name: Replace old solc binaries host + run: | + sudo echo "104.21.58.145 solc-bin.ethereum.org" | sudo tee -a /etc/hosts + - name: Run tests shell: bash run: > From 8076b31f6dcb641414ebb01b585c01aba739fbe7 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:31:44 +0000 Subject: [PATCH 158/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 6 +++--- sitecustomize.py | 9 --------- 2 files changed, 3 insertions(+), 12 deletions(-) delete mode 100644 sitecustomize.py diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index d2f2f6758..485b6c454 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -54,9 +54,9 @@ runs: shell: bash run: poetry run brownie networks import network-config.yaml True - - name: Replace old solc binaries host - run: | - sudo echo "104.21.58.145 solc-bin.ethereum.org" | sudo tee -a /etc/hosts + - name: Replace old solc binaries host + run: | + sudo echo "104.21.58.145 solc-bin.ethereum.org" | sudo tee -a /etc/hosts - name: Run tests shell: bash diff --git a/sitecustomize.py b/sitecustomize.py deleted file mode 100644 index 401970b51..000000000 --- a/sitecustomize.py +++ /dev/null @@ -1,9 +0,0 @@ -# sitecustomize.py -try: - import solcx.install as solcx_install -except ImportError: - # py-solc-x not installed in this environment; nothing to patch - pass -else: - # change this to your mirror / URL - solcx_install.BINARY_DOWNLOAD_BASE = "https://binaries.soliditylang.org/{}-amd64/{}" From f9d9e77045303de8bf0eed9fe6c9569e3597c0ea Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:33:34 +0000 Subject: [PATCH 159/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 485b6c454..53f2e3bf6 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -55,8 +55,8 @@ runs: run: poetry run brownie networks import network-config.yaml True - name: Replace old solc binaries host - run: | - sudo echo "104.21.58.145 solc-bin.ethereum.org" | sudo tee -a /etc/hosts + shell: bash + run: sudo echo "104.21.58.145 solc-bin.ethereum.org" | sudo tee -a /etc/hosts - name: Run tests shell: bash From 5370bd61ca1bcc736f5a70ff8ed2e7f26144a047 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:37:39 +0000 Subject: [PATCH 160/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 53f2e3bf6..3719bef8f 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -56,7 +56,7 @@ runs: - name: Replace old solc binaries host shell: bash - run: sudo echo "104.21.58.145 solc-bin.ethereum.org" | sudo tee -a /etc/hosts + run: echo 104.21.58.145 solc-bin.ethereum.org | sudo tee -a /etc/hosts - name: Run tests shell: bash From b48c2c50c2ce60a528103580797954b1ee2ec90a Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:42:23 +0000 Subject: [PATCH 161/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 3719bef8f..597ae7007 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -56,7 +56,7 @@ runs: - name: Replace old solc binaries host shell: bash - run: echo 104.21.58.145 solc-bin.ethereum.org | sudo tee -a /etc/hosts + run: echo 185.199.108.153 solc-bin.ethereum.org | sudo tee -a /etc/hosts - name: Run tests shell: bash From 565607769ac664a030202b26d87696ca31190c45 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:44:27 +0000 Subject: [PATCH 162/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 597ae7007..6124a2a3a 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -56,7 +56,7 @@ runs: - name: Replace old solc binaries host shell: bash - run: echo 185.199.108.153 solc-bin.ethereum.org | sudo tee -a /etc/hosts + run: echo 185.199.110.153 ethereum.org | sudo tee -a /etc/hosts - name: Run tests shell: bash From f688fa01b1e0f7911404787c7fd0ec3d39ea1105 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:47:57 +0000 Subject: [PATCH 163/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 6124a2a3a..ecf616048 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -56,7 +56,7 @@ runs: - name: Replace old solc binaries host shell: bash - run: echo 185.199.110.153 ethereum.org | sudo tee -a /etc/hosts + run: echo 185.199.110.153 solc-bin.ethereum.org | sudo tee -a /etc/hosts - name: Run tests shell: bash From b0e6a8f347d2564442020e03a50bdc326c766c76 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:50:35 +0000 Subject: [PATCH 164/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index ecf616048..63f308eb6 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -56,7 +56,7 @@ runs: - name: Replace old solc binaries host shell: bash - run: echo 185.199.110.153 solc-bin.ethereum.org | sudo tee -a /etc/hosts + run: echo 10.241.112.11 solc-bin.ethereum.org | sudo tee -a /etc/hosts - name: Run tests shell: bash From 5347e80cfc4cef428b3b7c9158a81211576812e6 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:56:02 +0000 Subject: [PATCH 165/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 63f308eb6..23da4a4d4 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -56,7 +56,7 @@ runs: - name: Replace old solc binaries host shell: bash - run: echo 10.241.112.11 solc-bin.ethereum.org | sudo tee -a /etc/hosts + run: echo 172.67.160.95 solc-bin.ethereum.org | sudo tee -a /etc/hosts - name: Run tests shell: bash From 8e1ad9cef6fe2853a2f927ea583bfbeafc03d180 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:57:49 +0000 Subject: [PATCH 166/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 23da4a4d4..46503dacb 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -56,7 +56,7 @@ runs: - name: Replace old solc binaries host shell: bash - run: echo 172.67.160.95 solc-bin.ethereum.org | sudo tee -a /etc/hosts + run: echo 188.114.97.1 solc-bin.ethereum.org | sudo tee -a /etc/hosts - name: Run tests shell: bash From fafb493941d460f297829e2a64406d961c8f4882 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 12:59:09 +0000 Subject: [PATCH 167/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 46503dacb..a105b9ec6 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -56,7 +56,7 @@ runs: - name: Replace old solc binaries host shell: bash - run: echo 188.114.97.1 solc-bin.ethereum.org | sudo tee -a /etc/hosts + run: echo 188.114.96.1 solc-bin.ethereum.org | sudo tee -a /etc/hosts - name: Run tests shell: bash From ead89f81e4c4e5b728ce7e363f99cc299c9f5a44 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 13:25:47 +0000 Subject: [PATCH 168/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 4 ---- .github/workflows/normal_vote_ci.yml | 4 ++-- sitecustomize.py | 11 +++++++++++ 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 sitecustomize.py diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index a105b9ec6..8572318c6 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -54,10 +54,6 @@ runs: shell: bash run: poetry run brownie networks import network-config.yaml True - - name: Replace old solc binaries host - shell: bash - run: echo 188.114.96.1 solc-bin.ethereum.org | sudo tee -a /etc/hosts - - name: Run tests shell: bash run: > diff --git a/.github/workflows/normal_vote_ci.yml b/.github/workflows/normal_vote_ci.yml index 087c4535d..b810c9b27 100644 --- a/.github/workflows/normal_vote_ci.yml +++ b/.github/workflows/normal_vote_ci.yml @@ -32,7 +32,7 @@ jobs: vote: "normal" rpc_url: ${{ secrets.ETH_RPC_URL }} etherscan: ${{ secrets.ETHERSCAN_TOKEN }} - command: "make test-1/2" + command: "PYTHONPATH=$PWD make test-1/2" run-tests-normal-2: name: Brownie fork NORMAL tests 2 @@ -56,4 +56,4 @@ jobs: vote: "normal" rpc_url: ${{ secrets.ETH_RPC_URL }} etherscan: ${{ secrets.ETHERSCAN_TOKEN }} - command: "make test-2/2" + command: "PYTHONPATH=$PWD make test-2/2" diff --git a/sitecustomize.py b/sitecustomize.py new file mode 100644 index 000000000..f42a44ba1 --- /dev/null +++ b/sitecustomize.py @@ -0,0 +1,11 @@ +# sitecustomize.py + +print("======================================================================applied") +try: + import solcx.install as solcx_install +except ImportError: + # py-solc-x not installed in this environment; nothing to patch + pass +else: + # change this to your mirror / URL + solcx_install.BINARY_DOWNLOAD_BASE = "https://binaries.soliditylang.org/{}-amd64/{}" \ No newline at end of file From f3975a0b44704cd82e11f79887e0053d7901a315 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 13:31:21 +0000 Subject: [PATCH 169/178] fix: download solc for CI --- .github/workflows/core_tests.yml | 1 + .github/workflows/dual_governance_regression.yml | 1 + .github/workflows/large_vote_ci.yml | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index a702ede19..a526c6f61 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -10,6 +10,7 @@ on: - "feat/rc1" - "feat/next-vote" - "feat/v3-vote" + - "ci-solc-download" schedule: - cron: "0 0 * * TUE" diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index 2b7656567..b222633a5 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -14,6 +14,7 @@ on: - "feat/rc1" - "feat/next-vote" - "feat/v3-vote" + - "ci-solc-download" workflow_dispatch: jobs: diff --git a/.github/workflows/large_vote_ci.yml b/.github/workflows/large_vote_ci.yml index 004819bf2..d125956dc 100644 --- a/.github/workflows/large_vote_ci.yml +++ b/.github/workflows/large_vote_ci.yml @@ -36,7 +36,7 @@ jobs: vote: "large" rpc_url: ${{ secrets.ETH_RPC_URL }} etherscan: ${{ secrets.ETHERSCAN_TOKEN }} - command: "make test-1/2" + command: "PYTHONPATH=$PWD make test-1/2" run-tests-large-2: name: Brownie fork LARGE tests 2 @@ -62,4 +62,4 @@ jobs: vote: "large" rpc_url: ${{ secrets.ETH_RPC_URL }} etherscan: ${{ secrets.ETHERSCAN_TOKEN }} - command: "make test-2/2" + command: "PYTHONPATH=$PWD make test-2/2" From 2d51ce61a55390bb83c29d2a7b7411c13277666a Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 13:36:33 +0000 Subject: [PATCH 170/178] fix: download solc for CI --- .github/workflows/core_tests.yml | 2 +- .github/workflows/dual_governance_regression.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index a526c6f61..7c8008b50 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v4 - name: Run init script - run: docker exec -e CORE_BRANCH tests-runner bash -c 'make init' + run: docker exec -e CORE_BRANCH tests-runner bash -c 'PYTHONPATH=$PWD make init' env: CORE_BRANCH: develop diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index b222633a5..b23a85e79 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -148,7 +148,7 @@ jobs: working-directory: scripts - name: Prepare test environment with Brownie - run: poetry run brownie run scripts/ci/prepare_environment --network mfh-1 + run: PYTHONPATH=$PWD poetry run brownie run scripts/ci/prepare_environment --network mfh-1 working-directory: scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From bcbbde4b36584fb427b2834db2a83ca3f7f36c51 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 13:42:37 +0000 Subject: [PATCH 171/178] fix: download solc for CI --- .github/actions/brownie_fork_tests/action.yml | 15 +++++++++++++++ .github/workflows/core_tests.yml | 1 - .github/workflows/dual_governance_regression.yml | 1 - sitecustomize.py | 4 +--- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/actions/brownie_fork_tests/action.yml b/.github/actions/brownie_fork_tests/action.yml index 8572318c6..154eb024e 100644 --- a/.github/actions/brownie_fork_tests/action.yml +++ b/.github/actions/brownie_fork_tests/action.yml @@ -54,6 +54,21 @@ runs: shell: bash run: poetry run brownie networks import network-config.yaml True + - name: Show system info + shell: bash + run: | + echo "Memory and swap:" + free -h + echo + swapon --show + echo + df -h + echo + echo "CPU units" + nproc --all + echo "Vote type" + echo ${{ inputs.vote }} + - name: Run tests shell: bash run: > diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index 7c8008b50..1ab431351 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -10,7 +10,6 @@ on: - "feat/rc1" - "feat/next-vote" - "feat/v3-vote" - - "ci-solc-download" schedule: - cron: "0 0 * * TUE" diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index b23a85e79..6cbf75777 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -14,7 +14,6 @@ on: - "feat/rc1" - "feat/next-vote" - "feat/v3-vote" - - "ci-solc-download" workflow_dispatch: jobs: diff --git a/sitecustomize.py b/sitecustomize.py index f42a44ba1..1f403eb18 100644 --- a/sitecustomize.py +++ b/sitecustomize.py @@ -1,11 +1,9 @@ # sitecustomize.py - -print("======================================================================applied") try: import solcx.install as solcx_install except ImportError: # py-solc-x not installed in this environment; nothing to patch pass else: - # change this to your mirror / URL + # replace outdated solc binaries URL with the current one solcx_install.BINARY_DOWNLOAD_BASE = "https://binaries.soliditylang.org/{}-amd64/{}" \ No newline at end of file From 47cbef0f1e8fad1254a1315902a827d74cd46790 Mon Sep 17 00:00:00 2001 From: dry914 Date: Wed, 10 Dec 2025 17:39:48 +0300 Subject: [PATCH 172/178] test: v3 voting review fixes --- scripts/upgrade_2025_12_10_mainnet_v3.py | 5 ++++- tests/utils_test_2025_12_10_lidov3.py | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_10_mainnet_v3.py index 8690fa0b6..435201679 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_10_mainnet_v3.py @@ -19,7 +19,10 @@ 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT in OracleDaemonConfig 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT in OracleDaemonConfig 1.17. Revoke CONFIG_MANAGER_ROLE from Agent on OracleDaemonConfig -1.18. Call V3Template.finishUpgrade() +1.18. Grant PAUSE_ROLE to Agent +1.19. Pause PredepositGuarantee +1.20. Revoke PAUSE_ROLE from Agent +1.21. Call V3Template.finishUpgrade() === NON-DG ITEMS === 2. Add AlterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid.alterTiers) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index 80cc4ef70..a581436fa 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -24,6 +24,7 @@ from utils.test.event_validators.aragon import validate_aragon_set_app_event, validate_aragon_grant_permission_event, validate_aragon_revoke_permission_event from utils.test.easy_track_helpers import _encode_calldata, create_and_enact_motion from utils.test.event_validators.unpause import validate_pause_for_event +from utils.test.event_validators.time_constraints import validate_dg_time_constraints_executed_within_day_time_event # ============================================================================ @@ -409,9 +410,7 @@ def encode_proxy_upgrade_to(proxy_contract, new_impl_address): # 1.19. Pause PredepositGuarantee agent_forward([ - (predeposit_guarantee.address, predeposit_guarantee.pauseFor.encode_input( - web3.codec.encode(['uint256'], [PAUSE_INFINITELY]) - )) + (predeposit_guarantee.address, predeposit_guarantee.pauseFor.encode_input(PAUSE_INFINITELY)) ]), # 1.20. Revoke PredepositGuarantee's PAUSE_ROLE from Agent @@ -695,6 +694,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert upgradeTemplate.initialOldBurnerStethSharesBalance() == 0, "V3Template should have initialOldBurnerStethSharesBalance 0 before startUpgrade" initial_total_shares_before = lido.getTotalShares() initial_total_pooled_ether_before = lido.getTotalPooledEther() + initial_burner_steth_shares_balance_before = lido.sharesOf(OLD_BURNER) # Step 1.3: Check Lido Locator implementation initial state assert locator_impl_before != LIDO_LOCATOR_IMPL, "Locator implementation should be different before upgrade" @@ -748,6 +748,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # Step 1.19. Pause PredepositGuarantee assert predeposit_guarantee.isPaused() == False, "PredepositGuarantee should not be paused before upgrade" + assert predeposit_guarantee.getResumeSinceTimestamp() == 0, "PredepositGuarantee should have getResumeSinceTimestamp 0 before upgrade" # Step 1.21. Call V3Template.finishUpgrade assert lido.getContractVersion() == NEW_LIDO_VERSION - 1, "LIDO should have version 2 before finishUpgrade" @@ -783,6 +784,14 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # === DG EXECUTION EVENTS VALIDATION === + # 1.1. Check execution time window (14:00–23:00 UTC) + validate_dg_time_constraints_executed_within_day_time_event( + dg_events[0], + UTC14, + UTC23, + emitted_by=DUAL_GOVERNANCE_TIME_CONSTRAINTS + ) + # 1.2. Call V3Template.startUpgrade validate_upgrade_started_event(dg_events[1]) @@ -943,6 +952,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): assert upgradeTemplate.upgradeBlockNumber() != 0, "V3Template should have upgradeBlockNumber not 0 after startUpgrade" assert upgradeTemplate.initialTotalShares() == initial_total_shares_before, "V3Template should have initialTotalShares equal to the initial total shares before upgrade" assert upgradeTemplate.initialTotalPooledEther() == initial_total_pooled_ether_before, "V3Template should have initialTotalPooledEther equal to the initial total pooled ether before upgrade" + assert upgradeTemplate.initialOldBurnerStethSharesBalance() == initial_burner_steth_shares_balance_before, "V3Template should have initialOldBurnerStethSharesBalance equal to the initial burner steth shares balance before upgrade" # Step 1.3: Validate Lido Locator implementation was updated assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" @@ -985,6 +995,7 @@ def enact_and_test_dg(stranger, expected_dg_proposal_id): # Step 1.19. Pause PredepositGuarantee assert predeposit_guarantee.isPaused() == True, "PredepositGuarantee should be paused after upgrade" + assert predeposit_guarantee.getResumeSinceTimestamp() == PAUSE_INFINITELY, "PredepositGuarantee should have getResumeSinceTimestamp PAUSE_INFINITELY after upgrade" # Step 1.20. Revoke PredepositGuarantee's PAUSE_ROLE from Agent assert not predeposit_guarantee.hasRole(pdg_pause_role, AGENT), "PredepositGuarantee should not have PAUSE_ROLE on Agent after upgrade" From d0a812eb113cd1a721ccd67b6e1293d93de2b02b Mon Sep 17 00:00:00 2001 From: dry914 Date: Wed, 10 Dec 2025 18:03:01 +0300 Subject: [PATCH 173/178] test: refactor v3 et tests --- tests/utils_test_2025_12_10_lidov3.py | 45 +++++++++++++++------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_10_lidov3.py index a581436fa..28c02b0db 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_10_lidov3.py @@ -1248,6 +1248,8 @@ def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger, def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory): + initial_total_value = 2 * 10**18 + # First create the vault creation_tx = vault_factory.createVaultWithDashboard( stranger, @@ -1256,7 +1258,7 @@ def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, strang 100, 3600, # 1 hour [], - {"from": stranger, "value": "1 ether"}, + {"from": stranger, "value": initial_total_value}, ) vault = creation_tx.events["VaultCreated"][0]["vault"] @@ -1301,8 +1303,8 @@ def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, strang vault_hub.applyVaultReport( vault, current_time, - 2 * 10**18, - 2 * 10**18, + initial_total_value, + initial_total_value, 0, 0, 0, @@ -1324,8 +1326,10 @@ def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, strang def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory): + initial_total_value = 2 * 10**18 + # top up VAULTS_ADAPTER - stranger.transfer(VAULTS_ADAPTER, 2 * 10**18) + stranger.transfer(VAULTS_ADAPTER, 10**18) pubkey = b"01" * 48 # First create the vault @@ -1336,7 +1340,7 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange 100, 3600, # 1 hour [], - {"from": stranger, "value": "1 ether"}, + {"from": stranger, "value": initial_total_value}, ) vault = creation_tx.events["VaultCreated"][0]["vault"] @@ -1376,9 +1380,9 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange vault_hub.applyVaultReport( vault, current_time, - 2 * 10**18, - 2 * 10**18, - 7 * 10**18, + initial_total_value, + initial_total_value, + 4 * initial_total_value, 0, 0, 0, @@ -1400,6 +1404,9 @@ def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, strange def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, operator_grid, lazy_oracle, vault_hub, vault_factory): + initial_total_value = 2 * 10**18 + max_shares_to_socialize = 2 * 10**16 + # Enable minting in default group executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) operator_grid.alterTiers([0], [(100_000 * 10**18, 300, 250, 50, 40, 10)], {"from": executor}) @@ -1412,7 +1419,7 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, 100, 3600, # 1 hour [], - {"from": stranger, "value": "2 ether"}, + {"from": stranger, "value": initial_total_value}, ) bad_debt_vault = creation_tx.events["VaultCreated"][0]["vault"] @@ -1424,8 +1431,8 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, vault_hub.applyVaultReport( bad_debt_vault, current_time, - 2 * 10**18, - 2 * 10**18, + initial_total_value, + initial_total_value, 0, 0, 0, @@ -1433,7 +1440,7 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, {"from": lazy_oracle_account}) bad_debt_dashboard = accounts.at(creation_tx.events["DashboardCreated"][0]["dashboard"], force=True) - vault_hub.mintShares(bad_debt_vault, stranger, 5 * 10**17, {"from": bad_debt_dashboard}) + vault_hub.mintShares(bad_debt_vault, stranger, 10 * max_shares_to_socialize, {"from": bad_debt_dashboard}) creation_tx = vault_factory.createVaultWithDashboard( stranger, @@ -1442,12 +1449,10 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, 100, 3600, # 1 hour [], - {"from": stranger, "value": "2 ether"}, + {"from": stranger, "value": initial_total_value}, ) vault_acceptor = creation_tx.events["VaultCreated"][0]["vault"] - max_shares_to_socialize = 1 * 10**16 - calldata = _encode_calldata(["address[]", "address[]", "uint256[]"], [[bad_debt_vault], [vault_acceptor], [max_shares_to_socialize]]) motions_before = easy_track.getMotions() @@ -1477,8 +1482,8 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, vault_hub.applyVaultReport( vault_acceptor, current_time, - 2 * 10**18, - 2 * 10**18, + initial_total_value, + initial_total_value, 0, 0, 0, @@ -1489,10 +1494,10 @@ def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, vault_hub.applyVaultReport( bad_debt_vault, current_time, - 1 * 10**17, - 2 * 10**18, + 10 * max_shares_to_socialize, + initial_total_value, 0, - 2 * 10**18, + initial_total_value, 0, 0, {"from": lazy_oracle_account}) From 3dd790bcf80c14b9cd8c5673fb7e9beb15cb175f Mon Sep 17 00:00:00 2001 From: dry914 Date: Wed, 10 Dec 2025 19:29:59 +0300 Subject: [PATCH 174/178] rename to votings to 15 dec --- ...12_10_mainnet_v3.py => upgrade_2025_12_15_mainnet_v3.py} | 2 +- scripts/{vote_2025_12_10.py => vote_2025_12_15.py} | 6 +++--- tests/{test_2025_12_10.py => test_2025_12_15.py} | 4 ++-- ...2025_12_10_lidov3.py => utils_test_2025_12_15_lidov3.py} | 2 +- ...10_operations.py => utils_test_2025_12_15_operations.py} | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) rename scripts/{upgrade_2025_12_10_mainnet_v3.py => upgrade_2025_12_15_mainnet_v3.py} (99%) rename scripts/{vote_2025_12_10.py => vote_2025_12_15.py} (99%) rename tests/{test_2025_12_10.py => test_2025_12_15.py} (95%) rename tests/{utils_test_2025_12_10_lidov3.py => utils_test_2025_12_15_lidov3.py} (99%) rename tests/{utils_test_2025_12_10_operations.py => utils_test_2025_12_15_operations.py} (99%) diff --git a/scripts/upgrade_2025_12_10_mainnet_v3.py b/scripts/upgrade_2025_12_15_mainnet_v3.py similarity index 99% rename from scripts/upgrade_2025_12_10_mainnet_v3.py rename to scripts/upgrade_2025_12_15_mainnet_v3.py index 435201679..5ae222b37 100644 --- a/scripts/upgrade_2025_12_10_mainnet_v3.py +++ b/scripts/upgrade_2025_12_15_mainnet_v3.py @@ -1,5 +1,5 @@ """ -# Vote 2025_12_10 +# Vote 2025_12_15 === 1. DG PROPOSAL === 1.1. Check execution time window (14:00–23:00 UTC) diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_15.py similarity index 99% rename from scripts/vote_2025_12_10.py rename to scripts/vote_2025_12_15.py index 99b7354dc..bf69b8096 100644 --- a/scripts/vote_2025_12_10.py +++ b/scripts/vote_2025_12_15.py @@ -1,5 +1,5 @@ """ -# Vote 2025_12_10 +# Vote 2025_12_15 === 1. DG PROPOSAL === I. Change Curated Module fees @@ -251,11 +251,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: ), ]), ] - + dg_call_script = submit_proposals([ (dg_items, "Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, set Easy Track TRP limit") ]) - + vote_desc_items, call_script_items = zip( ( "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, set Easy Track TRP limit", diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_15.py similarity index 95% rename from tests/test_2025_12_10.py rename to tests/test_2025_12_15.py index c03ac68bb..f74348c02 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_15.py @@ -1,7 +1,7 @@ import pytest import brownie -import tests.utils_test_2025_12_10_lidov3 as lidov3 -import tests.utils_test_2025_12_10_operations as ops +import tests.utils_test_2025_12_15_lidov3 as lidov3 +import tests.utils_test_2025_12_15_operations as ops @pytest.fixture(autouse=True) def isolation(): diff --git a/tests/utils_test_2025_12_10_lidov3.py b/tests/utils_test_2025_12_15_lidov3.py similarity index 99% rename from tests/utils_test_2025_12_10_lidov3.py rename to tests/utils_test_2025_12_15_lidov3.py index 28c02b0db..f13d6031f 100644 --- a/tests/utils_test_2025_12_10_lidov3.py +++ b/tests/utils_test_2025_12_15_lidov3.py @@ -30,7 +30,7 @@ # ============================================================================ # ============================== Import vote ================================= # ============================================================================ -from scripts.upgrade_2025_12_10_mainnet_v3 import start_vote, get_vote_items +from scripts.upgrade_2025_12_15_mainnet_v3 import start_vote, get_vote_items # ============================================================================ diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_15_operations.py similarity index 99% rename from tests/utils_test_2025_12_10_operations.py rename to tests/utils_test_2025_12_15_operations.py index 49692375c..10ba176f3 100644 --- a/tests/utils_test_2025_12_10_operations.py +++ b/tests/utils_test_2025_12_15_operations.py @@ -46,7 +46,7 @@ class TokenLimit(NamedTuple): # ============================== Import vote ================================= -from scripts.vote_2025_12_10 import start_vote, get_vote_items +from scripts.vote_2025_12_15 import start_vote, get_vote_items # ============================== Addresses =================================== From baa4886574e35206f484991b78e7f48580dcea1d Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 12 Dec 2025 09:36:23 +0000 Subject: [PATCH 175/178] fix-tests-solc-url --- Makefile | 4 ++-- scripts/upgrade_2025_12_15_mainnet_v3.py | 20 ++++++++++++++++---- tests/utils_test_2025_12_15_lidov3.py | 4 ++-- tests/utils_test_2025_12_15_operations.py | 20 ++++++-------------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index b10dbef61..b29610ff0 100644 --- a/Makefile +++ b/Makefile @@ -44,8 +44,8 @@ init-scripts: # because current brownie version does not support pm install from the config file poetry install && \ yarn && \ - poetry run brownie pm install OpenZeppelin/openzeppelin-contracts@4.0.0 && \ - poetry run brownie compile && \ + PYTHONPATH=$PWD poetry run brownie pm install OpenZeppelin/openzeppelin-contracts@4.0.0 && \ + PYTHONPATH=$PWD poetry run brownie compile && \ poetry run brownie networks import network-config.yaml True debug: diff --git a/scripts/upgrade_2025_12_15_mainnet_v3.py b/scripts/upgrade_2025_12_15_mainnet_v3.py index 5ae222b37..b9b0a4c69 100644 --- a/scripts/upgrade_2025_12_15_mainnet_v3.py +++ b/scripts/upgrade_2025_12_15_mainnet_v3.py @@ -52,10 +52,22 @@ # ============================= Description ================================== -# TODO -IPFS_DESCRIPTION = "omni dec 2025" -DG_PROPOSAL_DESCRIPTION = "Activate Lido V3" -DG_SUBMISSION_DESCRIPTION = "1. Submit a Dual Governance proposal to activate Lido V3" +IPFS_DESCRIPTION = """ +**Activate Lido V3: Phase 1 (Soft Launch)** — a major upgrade to the Lido protocol introduces non-custodial, over-collateralized staking vaults (“stVaults”) that enable stakers to opt into specific operators or strategies while still minting stETH. The Lido V3 design and implementation follow the DAO-approved [Snapshot](https://snapshot.box/#/s:lido-snapshot.eth/proposal/0x01cd474645cc7c3ddf68314d475d421ef833499297f508fee5f7411fafff3954). Phase 1 is intentionally [proposed in a constrained soft launch](https://research.lido.fi/t/lido-v3-design-implementation-proposal/10665/8) mode to enable early adopters and partners while maintaining a conservative security posture. + +Deployment verification: [MixBytes](https://github.com/lidofinance/audits/blob/main/MixBytes%20Lido%20V3%20Security%20Audit%20Report%20-%2012-2025.pdf) | Formal verification: [Certora](https://github.com/lidofinance/audits/blob/main/Certora%20Lido%20V3%20Formal%20Verification%20Report%20-%2012-2025.pdf) | Audits: [MixBytes](https://github.com/lidofinance/audits/blob/main/MixBytes%20Lido%20V3%20Security%20Audit%20Report%20-%2012-2025.pdf), [Certora](https://github.com/lidofinance/audits/blob/main/Certora%20Lido%20V3%20Audit%20Report%20-%2012-2025.pdf), [Consensys Diligence](https://github.com/lidofinance/audits/blob/main/Consensys%20Diligence%20Lido%20V3%20Security%20Audit%20-%2011-2025.pdf) | Offchain audits: [Certora](https://github.com/lidofinance/audits/blob/main/Certora%20Lido%20V3%20Oracle%20V7%20Audit%20Report%20-%2012-2025.pdf), [Composable Security](https://github.com/lidofinance/audits/blob/main/Composable%20Security%20Lido%20V3%20Oracle%20V7%20Audit%20Report%20-%2012-2025.pdf) + +[Dual Governance Items](https://research.lido.fi/t/lido-v3-design-implementation-proposal/10665/5#p-23638-part-2-dual-governance-items-21-items-subject-to-dg-veto-period-27) +- Ensure DG proposal execution occurs during the monitored time window and aligns with oracle reports. Item 1.1. +- Lock upgrade window and validate network state. Item 1.2. +- Upgrade proxy implementations, reassign roles and permissions, configure oracle slashing parameters. Items 1.3-1.17. +- Disable Predeposit Guarantee guided deposit flows as a part of [Soft Launch](https://research.lido.fi/t/lido-v3-design-implementation-proposal/10665/9). Items 1.18-1.20. +- Finalize upgrade and validate Lido V3 activation state. Item 1.21. + +[Voting Items](https://research.lido.fi/t/lido-v3-design-implementation-proposal/10665/5#p-23638-part-1-voting-items-8-items-execute-immediately-26) +- Add Easy Track factories enabling [stVault Committee](https://docs.lido.fi/multisigs/committees#216-stvaults-committee) to configure VaultHub and OperatorGrid contracts. Items 2–9.""" +DG_PROPOSAL_DESCRIPTION = "Activate Lido V3: Phase 1 (Soft Launch)" +DG_SUBMISSION_DESCRIPTION = "1. Submit a Dual Governance proposal to activate Lido V3: Phase 1 (Soft Launch)" # ================================ Main ====================================== diff --git a/tests/utils_test_2025_12_15_lidov3.py b/tests/utils_test_2025_12_15_lidov3.py index f13d6031f..09e3b97c0 100644 --- a/tests/utils_test_2025_12_15_lidov3.py +++ b/tests/utils_test_2025_12_15_lidov3.py @@ -455,7 +455,7 @@ def enact_and_test_voting( Includes all before/after voting checks and event validation. """ EXPECTED_VOTE_EVENTS_COUNT = 9 - IPFS_DESCRIPTION_HASH = "bafkreic4xuaowfowt7faxnngnzynv7biuo7guv4s4jrngngjzzxyz3up2i" + IPFS_DESCRIPTION_HASH = "bafkreibvblxc6urod5fefbfadebtr5fy26ixeliyzctcn6lnwlfk6xxvmm" # ======================================================================= # ========================= Arrange variables =========================== @@ -558,7 +558,7 @@ def enact_and_test_voting( proposal_id=expected_dg_proposal_id, proposer=VOTING, executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="Activate Lido V3", + metadata="Activate Lido V3: Phase 1 (Soft Launch)", proposal_calls=dual_governance_proposal_calls(), ) diff --git a/tests/utils_test_2025_12_15_operations.py b/tests/utils_test_2025_12_15_operations.py index 10ba176f3..982f467e8 100644 --- a/tests/utils_test_2025_12_15_operations.py +++ b/tests/utils_test_2025_12_15_operations.py @@ -676,8 +676,8 @@ def enact_and_test_voting( prepare_agent_for_steth_payment(2_000 * 10**18) # check ET limits via Easy Track motion - ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] // 10**18 - LEGO_LDO_SPENDABLE_BALANCE = interface.AllowedRecipientRegistry(LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY).getPeriodState({"from": AGENT})[1] + ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getLimitParameters({"from": AGENT})[0] // 10**18 + LEGO_LDO_SPENDABLE_BALANCE = interface.AllowedRecipientRegistry(LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY).getLimitParameters({"from": AGENT})[0] et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) @@ -975,7 +975,7 @@ def trp_limit_test(stranger): chain.snapshot() # sleep to January so that TRP limit period does not change (it depends when tests are run) - chain.sleep(30 * 24 * 60 * 60) + chain.sleep(20 * 24 * 60 * 60) chain.mine() # spend all in several transfers @@ -1017,17 +1017,9 @@ def et_limit_test(stranger, token, max_spend_at_once, to_spend, TRUSTED_CALLER, chain.snapshot() - # check that there is no way to spend more then expected - with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): - create_and_enact_payment_motion( - easy_track, - TRUSTED_CALLER, - TOP_UP_ALLOWED_RECIPIENTS_FACTORY, - token, - [trusted_caller_account], - [to_spend + 1], - stranger, - ) + # sleep to January so that ET limit period does not change (it depends when tests are run) + chain.sleep(20 * 24 * 60 * 60) + chain.mine() # spend all in several transfers recipients = [] From 12934a4543d0dbe06b1a36bccfd1fe63c40b314c Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 12 Dec 2025 09:48:34 +0000 Subject: [PATCH 176/178] fix: python var --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b29610ff0..f9a252d6c 100644 --- a/Makefile +++ b/Makefile @@ -44,8 +44,8 @@ init-scripts: # because current brownie version does not support pm install from the config file poetry install && \ yarn && \ - PYTHONPATH=$PWD poetry run brownie pm install OpenZeppelin/openzeppelin-contracts@4.0.0 && \ - PYTHONPATH=$PWD poetry run brownie compile && \ + PYTHONPATH=$$PWD poetry run brownie pm install OpenZeppelin/openzeppelin-contracts@4.0.0 && \ + PYTHONPATH=$$PWD poetry run brownie compile && \ poetry run brownie networks import network-config.yaml True debug: From 0ae2abcd2feda96e9e80910bb65b959ca411d1e6 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 12 Dec 2025 11:21:51 +0000 Subject: [PATCH 177/178] fix: a41 test --- tests/utils_test_2025_12_15_operations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/utils_test_2025_12_15_operations.py b/tests/utils_test_2025_12_15_operations.py index 982f467e8..aae9f18c7 100644 --- a/tests/utils_test_2025_12_15_operations.py +++ b/tests/utils_test_2025_12_15_operations.py @@ -811,7 +811,6 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): # Item 1.3 a41_summary_before = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) assert a41_summary_before['targetLimitMode'] == NO_TARGET_LIMIT_MODE_BEFORE - assert a41_summary_before['depositableValidatorsCount'] > 0 assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" # Items 1.4 From 0fce454f66628d023fbb46bff399e6b60c097312 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Sun, 14 Dec 2025 17:55:45 +0000 Subject: [PATCH 178/178] fix: trp --- tests/utils_test_2025_12_15_operations.py | 24 +++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/utils_test_2025_12_15_operations.py b/tests/utils_test_2025_12_15_operations.py index aae9f18c7..98ec2e6ae 100644 --- a/tests/utils_test_2025_12_15_operations.py +++ b/tests/utils_test_2025_12_15_operations.py @@ -126,6 +126,18 @@ class TokenLimit(NamedTuple): TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC TRP_PERIOD_DURATION_MONTHS = 12 +def get_trp_period_start(): + period_start_timestamp_for_trp = TRP_PERIOD_START_TIMESTAMP + if chain.time() >= 1767225600: # Thu Jan 01 2026 00:00:00 GMT+0000 + period_start_timestamp_for_trp = 1767225600 # Thu Jan 01 2026 00:00:00 GMT+0000 + return period_start_timestamp_for_trp + +def get_trp_period_end(): + period_end_timestamp_for_trp = TRP_PERIOD_END_TIMESTAMP + if chain.time() >= 1767225600: # Thu Jan 01 2026 00:00:00 GMT+0000 + period_end_timestamp_for_trp = 1798761600 # Fri Jan 01 2027 00:00:00 GMT+0000 + return period_end_timestamp_for_trp + ALLOWED_TOKENS_BEFORE = 3 ALLOWED_TOKENS_AFTER = 4 @@ -873,7 +885,7 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): dg_events[3], limit=TRP_LIMIT_AFTER, period_duration_month=TRP_PERIOD_DURATION_MONTHS, - period_start_timestamp=TRP_PERIOD_START_TIMESTAMP, + period_start_timestamp=get_trp_period_start(), emitted_by=ET_TRP_REGISTRY, ) @@ -956,8 +968,8 @@ def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): if TRP_ALREADY_SPENT_AFTER is not None: assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER - assert trp_period_start_after == TRP_PERIOD_START_TIMESTAMP - assert trp_period_end_after == TRP_PERIOD_END_TIMESTAMP + assert trp_period_start_after == get_trp_period_start() + assert trp_period_end_after == get_trp_period_end() # scenraio test for TRP ET factory behavior after the vote trp_limit_test(stranger) @@ -967,7 +979,6 @@ def trp_limit_test(stranger): easy_track = interface.EasyTrack(EASY_TRACK) ldo_token = interface.ERC20(LDO_TOKEN) - to_spend = 15_000_000 * 10**18 max_spend_at_once = 5_000_000 * 10**18 trp_committee_account = accounts.at(TRP_COMMITTEE, force=True) @@ -977,6 +988,11 @@ def trp_limit_test(stranger): chain.sleep(20 * 24 * 60 * 60) chain.mine() + _, spendableBalanceInPeriod, periodStartTimestamp, periodEndTimestamp = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY).getPeriodState({"from": AGENT}) + to_spend = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY).getLimitParameters({"from": AGENT})[0] + if chain.time() >= periodStartTimestamp and chain.time() < periodEndTimestamp: + to_spend = spendableBalanceInPeriod + # spend all in several transfers recipients = [] amounts = []