diff --git a/archive/scripts/vote_2023_09_12.py b/archive/scripts/vote_2023_09_12.py new file mode 100644 index 00000000..3b56c499 --- /dev/null +++ b/archive/scripts/vote_2023_09_12.py @@ -0,0 +1,90 @@ +""" +Voting 164 12/09/2023 +Vote REJECTED +""" + +import time + +from typing import Dict + +from brownie.network.transaction import TransactionReceipt +from brownie import web3, interface # type: ignore +from utils.agent import agent_forward + +from utils.voting import bake_vote_items, confirm_vote_script, create_vote + +from utils.config import ( + get_deployer_account, + get_is_live, + get_priority_fee, + contracts, +) + +from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description + +description = """ +The proposal is to support **Jump Crypto voluntarily exits from the validator set** by setting `targetValidatorsCount` to 0. +Algorithm would prioritise exiting Jump Crypto validators in order to fulfil users' withdrawals requests. +Jump Crypto request on [forum](https://research.lido.fi/t/lido-dao-proposal-to-set-targetvalidatorscount-for-jump-crypto-operator-to-0-to-wind-down-the-jump-crypto-legacy-set/5259). +""" + + +def start_vote(tx_params: Dict[str, str], silent: bool) -> bool | list[int | TransactionReceipt | None]: + """Prepare and run voting.""" + + # contracts.node_operators_registry.getNodeOperator(1, True) + JUMP_CRYPTO_ID = 1 + # web3.keccak(text="STAKING_MODULE_MANAGE_ROLE") + STAKING_MODULE_MANAGE_ROLE = "0x3105bcbf19d4417b73ae0e58d508a65ecf75665e46c2622d8521732de6080c48" + + call_script_items = [ + # 1. Support **Jump Crypto voluntarily exits from the validator set** by setting `targetValidatorsCount` to 0. + ## 1) Grant STAKING_MODULE_MANAGE_ROLE to Lido Agent + agent_forward( + [ + ( + contracts.staking_router.address, + contracts.staking_router.grantRole.encode_input( + STAKING_MODULE_MANAGE_ROLE, contracts.agent.address + ), + ) + ] + ), + ## 2) Set Jump Crypto targetValidatorsCount to 0 + agent_forward( + [ + ( + contracts.staking_router.address, + contracts.staking_router.updateTargetValidatorsLimits.encode_input(1, JUMP_CRYPTO_ID, True, 0), + ) + ] + ), + ] + + vote_desc_items = [ + f"1) Grant STAKING_MODULE_MANAGE_ROLE to Lido Agent", + f"2) Set Jump Crypto targetValidatorsLimits to 0", + ] + + vote_items = bake_vote_items(vote_desc_items, call_script_items) + + if silent: + desc_ipfs = calculate_vote_ipfs_description(description) + else: + desc_ipfs = upload_vote_ipfs_description(description) + + return confirm_vote_script(vote_items, silent, desc_ipfs) and list( + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) + ) + + +def main(): + tx_params = {"from": get_deployer_account()} + if get_is_live(): + tx_params["priority_fee"] = get_priority_fee() + + vote_id, _ = start_vote(tx_params=tx_params, silent=False) + + vote_id >= 0 and print(f"Vote created: {vote_id}.") + + time.sleep(5) # hack for waiting thread #2. diff --git a/archive/tests/test_2023_09_12.py b/archive/tests/test_2023_09_12.py new file mode 100644 index 00000000..296c22e5 --- /dev/null +++ b/archive/tests/test_2023_09_12.py @@ -0,0 +1,93 @@ +""" +Tests for voting 12/09/2023 + +""" +from archive.scripts.vote_2023_09_12 import start_vote + +from utils.config import ( + network_name, + contracts, + LDO_HOLDER_ADDRESS_FOR_TESTS, +) +from utils.voting import find_metadata_by_vote_id +from utils.ipfs import get_lido_vote_cid_from_str +from utils.test.tx_tracing_helpers import * + +from utils.test.event_validators.node_operators_registry import ( + validate_target_validators_count_changed_event, + TargetValidatorsCountChanged, +) +from utils.test.event_validators.permission import validate_grant_role_event + + +def test_vote( + helpers, + bypass_events_decoding, + vote_ids_from_env, + accounts, +): + # params + agent = contracts.agent + nor = contracts.node_operators_registry + staking_router = contracts.staking_router + target_NO_id = 1 + target_validators_count_change_request = TargetValidatorsCountChanged( + nodeOperatorId=target_NO_id, targetValidatorsCount=0 + ) + + # web3.keccak(text="STAKING_MODULE_MANAGE_ROLE") + STAKING_MODULE_MANAGE_ROLE = "0x3105bcbf19d4417b73ae0e58d508a65ecf75665e46c2622d8521732de6080c48" + + # 1) + assert staking_router.hasRole(STAKING_MODULE_MANAGE_ROLE, agent.address) == False + + # 2) + NO_summary_before = nor.getNodeOperatorSummary(target_NO_id) + assert NO_summary_before[0] == False + assert NO_summary_before[1] == 0 + assert NO_summary_before[2] == 0 + assert NO_summary_before[3] == 0 + assert NO_summary_before[4] == 0 + assert NO_summary_before[5] == 0 + assert NO_summary_before[6] == 1000 + assert NO_summary_before[7] == 0 + + # START VOTE + if len(vote_ids_from_env) > 0: + (vote_id,) = vote_ids_from_env + else: + tx_params = {"from": LDO_HOLDER_ADDRESS_FOR_TESTS} + vote_id, _ = start_vote(tx_params, silent=True) + + vote_tx = helpers.execute_vote(accounts, vote_id, contracts.voting) + + print(f"voteId = {vote_id}, gasUsed = {vote_tx.gas_used}") + + # validate vote events + assert count_vote_items_by_events(vote_tx, contracts.voting) == 2, "Incorrect voting items count" + + metadata = find_metadata_by_vote_id(vote_id) + + assert get_lido_vote_cid_from_str(metadata) == "bafkreiapvuobyrudww3oqhfopbs2fdmtebi6jnvpeb3plxkajnhafw25im" + # 1) + assert staking_router.hasRole(STAKING_MODULE_MANAGE_ROLE, agent.address) == True + + # 2) + NO_summary_after = nor.getNodeOperatorSummary(target_NO_id) + assert NO_summary_after[0] == True + assert NO_summary_after[1] == 0 + assert NO_summary_after[2] == 0 + assert NO_summary_after[3] == 0 + assert NO_summary_after[4] == 0 + assert NO_summary_after[5] == 0 + assert NO_summary_after[6] == 1000 + assert NO_summary_after[7] == 0 + + if bypass_events_decoding or network_name() in ("goerli", "goerli-fork"): + return + + evs = group_voting_events(vote_tx) + + validate_grant_role_event(evs[0], STAKING_MODULE_MANAGE_ROLE, agent.address, agent.address) + + validate_target_validators_count_changed_event(evs[1], target_validators_count_change_request) diff --git a/tests/README.md b/tests/README.md index f39099be..a6960693 100644 --- a/tests/README.md +++ b/tests/README.md @@ -42,7 +42,7 @@ expected changes. Snapshot tests work as follows: -1) Go over some protocol use scenario (e. g. stake by use + oracle report) +1) Go over some protocol use scenario (e.g. stake by use + oracle report) 2) Store the snapshot along the steps 3) Revert the chain changes 4) Execute the vote @@ -61,3 +61,27 @@ the voting without modification of the common test files ## Internal tests Internal tests are used to test the tooling itself. + +## For test debugging +How to run one test? +You need to add file name: +```shell +poetry run brownie test tests//test_.py -s +``` + +How not to raise the network every time you launch test? +You could to run network in separate terminal window, tests will connect to it: +```shell +poetry run brownie console --network mainnet-fork +``` + +How to decode unreadable error messages (like 0xb...)? +1) You need to clone `lido-cli` repo. +```shell +git clone https://github.com/lidofinance/lido-cli +``` +2) install following docs - https://github.com/lidofinance/lido-cli +3) run +```shell +./run.sh tx parse-error +``` diff --git a/tests/acceptance/test_node_operators_registry.py b/tests/acceptance/test_node_operators_registry.py index e86afde0..60b4353e 100644 --- a/tests/acceptance/test_node_operators_registry.py +++ b/tests/acceptance/test_node_operators_registry.py @@ -106,14 +106,21 @@ def test_nor_state(contract): assert node_operator["totalVettedValidators"] > 0 assert node_operator["totalVettedValidators"] <= node_operator["totalAddedValidators"] - if id == 22: - assert node_operator["totalExitedValidators"] == 11 + # counts could inc but not dec + if id == 2: + assert node_operator["totalExitedValidators"] == 252 + elif id == 4: + assert node_operator["totalExitedValidators"] == 174 + elif id == 5: + assert node_operator["totalExitedValidators"] == 36 elif id == 12: - assert node_operator["totalExitedValidators"] >= 134 + assert node_operator["totalExitedValidators"] == 2300 elif id == 21: - assert node_operator["totalExitedValidators"] >= 125 + assert node_operator["totalExitedValidators"] == 125 + elif id == 22: + assert node_operator["totalExitedValidators"] == 11 else: - assert node_operator["totalExitedValidators"] == 0 + assert node_operator["totalExitedValidators"] == 0, f"totalExitedValidators is positive for node {id}" assert node_operator["totalAddedValidators"] > 0 @@ -122,23 +129,34 @@ def test_nor_state(contract): assert node_operator["totalDepositedValidators"] <= node_operator["totalAddedValidators"] node_operator_summary = contract.getNodeOperatorSummary(id) - if id != 12: - assert node_operator_summary["isTargetLimitActive"] is False + exited_node_operators = [12] # vote 23-05-23 + if id in exited_node_operators: + assert ( + node_operator_summary["isTargetLimitActive"] is True + ), f"isTargetLimitActive is inactive for node {id}" else: - assert node_operator_summary["isTargetLimitActive"] is True + assert node_operator_summary["isTargetLimitActive"] is False, f"isTargetLimitActive is active for node {id}" assert node_operator_summary["targetValidatorsCount"] == 0 assert node_operator_summary["stuckValidatorsCount"] == 0 assert node_operator_summary["refundedValidatorsCount"] == 0 assert node_operator_summary["stuckPenaltyEndTimestamp"] == 0 - - if id == 22: - assert node_operator_summary["totalExitedValidators"] == 11 + # counts could inc but not dec + if id == 2: + assert node_operator_summary["totalExitedValidators"] == 252 + elif id == 4: + assert node_operator_summary["totalExitedValidators"] == 174 + elif id == 5: + assert node_operator_summary["totalExitedValidators"] == 36 elif id == 12: - assert node_operator_summary["totalExitedValidators"] >= 134 + assert node_operator_summary["totalExitedValidators"] == 2300 elif id == 21: - assert node_operator_summary["totalExitedValidators"] >= 125 + assert node_operator_summary["totalExitedValidators"] == 125 + elif id == 22: + assert node_operator_summary["totalExitedValidators"] == 11 else: - assert node_operator_summary["totalExitedValidators"] == 0 + assert ( + node_operator_summary["totalExitedValidators"] == 0 + ), f"totalExitedValidators is positive for node {id}" assert node_operator_summary["totalDepositedValidators"] > 0 assert node_operator_summary["depositableValidatorsCount"] is not None diff --git a/tests/regression/test_accounting_oracle_extra_data.py b/tests/regression/test_accounting_oracle_extra_data.py index 411abea3..f4eda0cf 100644 --- a/tests/regression/test_accounting_oracle_extra_data.py +++ b/tests/regression/test_accounting_oracle_extra_data.py @@ -3,6 +3,7 @@ from utils.test.oracle_report_helpers import oracle_report from utils.config import MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT, MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT +from utils.config import contracts @pytest.fixture() @@ -10,12 +11,24 @@ def extra_data_service(): return ExtraDataService() +def get_exited_count(node_operator_id): + counts = {2: 252, 4: 174, 5: 36} + if node_operator_id in counts.keys(): + return counts[node_operator_id] + else: + return 0 + + def test_accounting_oracle_too_node_ops_per_extra_data_item(extra_data_service): nos_per_item_count = 10 item_count = MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT + for i in range(nos_per_item_count): + no = contracts.node_operators_registry.getNodeOperator(i, True) + print(f'{i}-{no["totalExitedValidators"]}') + extra_data = extra_data_service.collect( {(1, i): i for i in range(nos_per_item_count)}, - {(1, i): i for i in range(nos_per_item_count)}, + {(1, i): get_exited_count(i) for i in range(nos_per_item_count)}, item_count, nos_per_item_count, ) diff --git a/tests/regression/test_all_round_happy_path.py b/tests/regression/test_all_round_happy_path.py index 93ae65f1..7358e29a 100644 --- a/tests/regression/test_all_round_happy_path.py +++ b/tests/regression/test_all_round_happy_path.py @@ -13,14 +13,11 @@ def test_all_round_happy_path(accounts, stranger, steth_holder, eth_whale): curated_module_id = 1 """ report """ - while ( - contracts.withdrawal_queue.getLastRequestId() - != contracts.withdrawal_queue.getLastFinalizedRequestId() - ): + while contracts.withdrawal_queue.getLastRequestId() != contracts.withdrawal_queue.getLastFinalizedRequestId(): # finalize all current requests first report_tx = oracle_report()[0] # stake new ether to increase buffer - contracts.lido.submit(ZERO_ADDRESS, { 'from': eth_whale.address, 'value': ETH(10000) }) + contracts.lido.submit(ZERO_ADDRESS, {"from": eth_whale.address, "value": ETH(10000)}) contracts.lido.approve(contracts.withdrawal_queue.address, 1000, {"from": steth_holder}) contracts.withdrawal_queue.requestWithdrawals([1000], steth_holder, {"from": steth_holder}) @@ -114,7 +111,7 @@ def test_all_round_happy_path(accounts, stranger, steth_holder, eth_whale): nor_operators_count = contracts.node_operators_registry.getNodeOperatorsCount() for i in range(nor_operators_count): no = contracts.node_operators_registry.getNodeOperator(i, True) - if (not no["totalDepositedValidators"]): + if not no["totalDepositedValidators"] or no["totalDepositedValidators"] == no["totalExitedValidators"]: nor_operators_count = nor_operators_count - 1 treasury_balance_before_rebase = contracts.lido.sharesOf(treasury) @@ -126,7 +123,9 @@ def test_all_round_happy_path(accounts, stranger, steth_holder, eth_whale): token_rebased_event = report_tx.events["TokenRebased"] transfer_event = report_tx.events["Transfer"] - assert extra_tx.events.count("Transfer") == nor_operators_count + assert ( + extra_tx.events.count("Transfer") == nor_operators_count + ), "extra_tx.events should have Transfer to all active operators, check activity condition above" assert report_tx.events.count("TokenRebased") == 1 assert report_tx.events.count("WithdrawalsFinalized") == 1 assert report_tx.events.count("StETHBurnt") == 1 diff --git a/tests/regression/test_pause_resume.py b/tests/regression/test_pause_resume.py index 03ba76b5..158f6890 100644 --- a/tests/regression/test_pause_resume.py +++ b/tests/regression/test_pause_resume.py @@ -2,7 +2,7 @@ import brownie import pytest -from brownie import ZERO_ADDRESS, web3, chain +from brownie import ZERO_ADDRESS, web3, chain, Contract from utils.config import contracts from utils.evm_script import encode_error @@ -10,7 +10,7 @@ from utils.test.oracle_report_helpers import oracle_report, prepare_exit_bus_report from utils.test.helpers import almostEqEth -DEPOSIT_AMOUNT = 100 * 10 ** 18 +DEPOSIT_AMOUNT = 100 * 10**18 @pytest.fixture() @@ -18,6 +18,11 @@ def stopped_lido(): contracts.lido.stop({"from": contracts.voting}) +@pytest.fixture(scope="module") +def burner() -> Contract: + return contracts.burner + + @pytest.fixture(scope="function", autouse=is_there_any_vote_scripts()) def autoexecute_vote(helpers, vote_ids_from_env, accounts): if vote_ids_from_env: @@ -63,10 +68,15 @@ def test_stop_resume_staking_lido_emit_events(self, helpers): def test_pause_resume_deposits_staking_module(self, helpers, stranger): tx = contracts.staking_router.pauseStakingModule(1, {"from": contracts.deposit_security_module}) - helpers.assert_single_event_named("StakingModuleStatusSet", tx, - {'setBy': contracts.deposit_security_module, - 'stakingModuleId': 1, - 'status': StakingModuleStatus.DepositsPaused}) + helpers.assert_single_event_named( + "StakingModuleStatusSet", + tx, + { + "setBy": contracts.deposit_security_module, + "stakingModuleId": 1, + "status": StakingModuleStatus.DepositsPaused, + }, + ) assert contracts.staking_router.getStakingModuleIsDepositsPaused(1) contracts.staking_router.grantRole( @@ -75,10 +85,11 @@ def test_pause_resume_deposits_staking_module(self, helpers, stranger): {"from": contracts.agent}, ) tx = contracts.staking_router.resumeStakingModule(1, {"from": stranger}) - helpers.assert_single_event_named("StakingModuleStatusSet", tx, - {'setBy': stranger, - 'stakingModuleId': 1, - 'status': StakingModuleStatus.Active}) + helpers.assert_single_event_named( + "StakingModuleStatusSet", + tx, + {"setBy": stranger, "stakingModuleId": 1, "status": StakingModuleStatus.Active}, + ) assert contracts.staking_router.getStakingModuleIsActive(1) def test_stop_staking_module(self, helpers, stranger): @@ -88,13 +99,12 @@ def test_stop_staking_module(self, helpers, stranger): {"from": contracts.agent}, ) - tx = contracts.staking_router.setStakingModuleStatus(1, - StakingModuleStatus.Stopped, - {"from": stranger}) - helpers.assert_single_event_named("StakingModuleStatusSet", tx, - {'setBy': stranger, - 'stakingModuleId': 1, - 'status': StakingModuleStatus.Stopped}) + tx = contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.Stopped, {"from": stranger}) + helpers.assert_single_event_named( + "StakingModuleStatusSet", + tx, + {"setBy": stranger, "stakingModuleId": 1, "status": StakingModuleStatus.Stopped}, + ) assert contracts.staking_router.getStakingModuleIsStopped(1) @@ -151,9 +161,11 @@ def test_revert_second_stop_resume(self): with brownie.reverts("CONTRACT_IS_ACTIVE"): contracts.lido.resume({"from": contracts.voting}) - @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.") + @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.voting}) @@ -168,7 +180,7 @@ def test_revert_second_pause_resume_staking(self): def test_revert_second_pause_resume_staking_module(self, stranger): contracts.staking_router.pauseStakingModule(1, {"from": contracts.deposit_security_module}) - with brownie.reverts(encode_error('StakingModuleNotActive()')): + with brownie.reverts(encode_error("StakingModuleNotActive()")): contracts.staking_router.pauseStakingModule(1, {"from": contracts.deposit_security_module}) contracts.staking_router.grantRole( @@ -178,7 +190,7 @@ def test_revert_second_pause_resume_staking_module(self, stranger): ) contracts.staking_router.resumeStakingModule(1, {"from": stranger}) - with brownie.reverts(encode_error('StakingModuleNotPaused()')): + with brownie.reverts(encode_error("StakingModuleNotPaused()")): contracts.staking_router.resumeStakingModule(1, {"from": stranger}) def test_revert_second_stop_staking_module(self, helpers, stranger): @@ -188,13 +200,9 @@ def test_revert_second_stop_staking_module(self, helpers, stranger): {"from": contracts.agent}, ) - contracts.staking_router.setStakingModuleStatus(1, - StakingModuleStatus.Stopped, - {"from": stranger}) - with brownie.reverts(encode_error('StakingModuleStatusTheSame()')): - contracts.staking_router.setStakingModuleStatus(1, - StakingModuleStatus.Stopped, - {"from": stranger}) + contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.Stopped, {"from": stranger}) + with brownie.reverts(encode_error("StakingModuleStatusTheSame()")): + contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.Stopped, {"from": stranger}) def test_revert_second_pause_resume_withdrawal_queue(self, helpers, stranger): inf = contracts.withdrawal_queue.PAUSE_INFINITELY() @@ -204,7 +212,7 @@ def test_revert_second_pause_resume_withdrawal_queue(self, helpers, stranger): {"from": contracts.agent}, ) contracts.withdrawal_queue.pauseFor(inf, {"from": stranger}) - with brownie.reverts(encode_error('ResumedExpected()')): + with brownie.reverts(encode_error("ResumedExpected()")): contracts.withdrawal_queue.pauseFor(inf, {"from": stranger}) contracts.withdrawal_queue.grantRole( @@ -213,7 +221,7 @@ def test_revert_second_pause_resume_withdrawal_queue(self, helpers, stranger): {"from": contracts.agent}, ) contracts.withdrawal_queue.resume({"from": stranger}) - with brownie.reverts(encode_error('PausedExpected()')): + with brownie.reverts(encode_error("PausedExpected()")): contracts.withdrawal_queue.resume({"from": stranger}) def test_revert_second_pause_resume_validators_exit_bus(self, helpers, stranger): @@ -224,7 +232,7 @@ def test_revert_second_pause_resume_validators_exit_bus(self, helpers, stranger) {"from": contracts.agent}, ) contracts.validators_exit_bus_oracle.pauseFor(inf, {"from": stranger}) - with brownie.reverts(encode_error('ResumedExpected()')): + with brownie.reverts(encode_error("ResumedExpected()")): contracts.validators_exit_bus_oracle.pauseFor(inf, {"from": stranger}) contracts.validators_exit_bus_oracle.grantRole( @@ -233,12 +241,13 @@ def test_revert_second_pause_resume_validators_exit_bus(self, helpers, stranger) {"from": contracts.agent}, ) contracts.validators_exit_bus_oracle.resume({"from": stranger}) - with brownie.reverts(encode_error('PausedExpected()')): + with brownie.reverts(encode_error("PausedExpected()")): contracts.validators_exit_bus_oracle.resume({"from": stranger}) # Lido contract tests + @pytest.mark.usefixtures("stopped_lido") def test_stopped_lido_cant_stake(stranger): with brownie.reverts("STAKING_PAUSED"): @@ -281,26 +290,35 @@ def test_paused_staking_can_report(): contracts.lido.pauseStaking({"from": contracts.voting}) oracle_report() + # Staking module tests + def test_paused_staking_module_cant_stake(): contracts.staking_router.pauseStakingModule(1, {"from": contracts.deposit_security_module}) - with brownie.reverts(encode_error('StakingModuleNotActive()')): + with brownie.reverts(encode_error("StakingModuleNotActive()")): contracts.lido.deposit(1, 1, "0x", {"from": contracts.deposit_security_module}), -def test_paused_staking_module_can_reward(): +def test_paused_staking_module_can_reward(burner: Contract): _, module_address, *_ = contracts.staking_router.getStakingModule(1) contracts.staking_router.pauseStakingModule(1, {"from": contracts.deposit_security_module}) shares_before = contracts.lido.sharesOf(module_address) (report_tx, _) = oracle_report() print(report_tx.events["Transfer"]) - assert report_tx.events["Transfer"][1]["to"] == module_address - assert report_tx.events["Transfer"][1]["from"] == ZERO_ADDRESS - assert report_tx.events["Transfer"][2]["to"] == contracts.agent - assert report_tx.events["Transfer"][2]["from"] == ZERO_ADDRESS - assert almostEqEth(report_tx.events["Transfer"][1]["value"], report_tx.events["Transfer"][1]["value"]) - assert report_tx.events["Transfer"][1]["value"] > 0 + module_index = 0 + if report_tx.events["Transfer"][module_index]["to"] == burner.address: + module_index += 1 + + agent_index = module_index + 1 + 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 almostEqEth( + report_tx.events["Transfer"][module_index]["value"], report_tx.events["Transfer"][agent_index]["value"] + ) + assert report_tx.events["Transfer"][module_index]["value"] > 0 def test_stopped_staking_module_cant_stake(stranger): @@ -310,9 +328,8 @@ def test_stopped_staking_module_cant_stake(stranger): {"from": contracts.agent}, ) - contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.Stopped, - {"from": stranger}) - with brownie.reverts(encode_error('StakingModuleNotActive()')): + contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.Stopped, {"from": stranger}) + with brownie.reverts(encode_error("StakingModuleNotActive()")): contracts.lido.deposit(1, 1, "0x", {"from": contracts.deposit_security_module}), @@ -323,25 +340,28 @@ def test_stopped_staking_module_cant_reward(stranger): {"from": contracts.agent}, ) _, module_address, *_ = contracts.staking_router.getStakingModule(1) - contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.Stopped, - {"from": stranger}) + contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.Stopped, {"from": stranger}) shares_before = contracts.lido.sharesOf(module_address) oracle_report() assert contracts.lido.sharesOf(module_address) == shares_before + def test_stopped_lido_cant_reward(stranger): contracts.lido.stop({"from": contracts.voting}) with brownie.reverts("CONTRACT_IS_STOPPED"): oracle_report() + # Withdrawal queue tests + def make_withdrawal_request(stranger): stranger.transfer(contracts.lido, DEPOSIT_AMOUNT) contracts.lido.approve(contracts.withdrawal_queue, DEPOSIT_AMOUNT - 1, {"from": stranger}) contracts.withdrawal_queue.requestWithdrawals([DEPOSIT_AMOUNT - 1], stranger, {"from": stranger}) + def pause_withdrawal_queue(stranger): contracts.withdrawal_queue.grantRole( web3.keccak(text="PAUSE_ROLE"), @@ -351,20 +371,24 @@ def pause_withdrawal_queue(stranger): inf = contracts.withdrawal_queue.PAUSE_INFINITELY() contracts.withdrawal_queue.pauseFor(inf, {"from": stranger}) + def test_paused_withdrawal_queue_cant_withdraw(stranger): pause_withdrawal_queue(stranger) - with brownie.reverts(encode_error('ResumedExpected()')): + with brownie.reverts(encode_error("ResumedExpected()")): make_withdrawal_request(stranger) + def test_paused_withdrawal_queue_can_stake(stranger): pause_withdrawal_queue(stranger) stranger.transfer(contracts.lido, DEPOSIT_AMOUNT) + def test_paused_withdrawal_queue_can_rebase(stranger): pause_withdrawal_queue(stranger) oracle_report() + def test_stopped_lido_cant_withdraw(stranger): stranger.transfer(contracts.lido, DEPOSIT_AMOUNT) contracts.lido.approve(contracts.withdrawal_queue, DEPOSIT_AMOUNT - 1, {"from": stranger}) @@ -377,13 +401,16 @@ def test_stopped_lido_cant_withdraw(stranger): # Validators exit bus tests + def prepare_report(): ref_slot, _ = contracts.hash_consensus_for_validators_exit_bus_oracle.getCurrentFrame() consensus_version = contracts.validators_exit_bus_oracle.getConsensusVersion() items, hash = prepare_exit_bus_report([], ref_slot) fast_lane_members, _ = contracts.hash_consensus_for_validators_exit_bus_oracle.getFastLaneMembers() for m in fast_lane_members: - contracts.hash_consensus_for_validators_exit_bus_oracle.submitReport(ref_slot, hash, consensus_version, {"from": m}) + contracts.hash_consensus_for_validators_exit_bus_oracle.submitReport( + ref_slot, hash, consensus_version, {"from": m} + ) return items, m @@ -396,6 +423,7 @@ def pause_validators_exit_bus(stranger): inf = contracts.validators_exit_bus_oracle.PAUSE_INFINITELY() contracts.validators_exit_bus_oracle.pauseFor(inf, {"from": stranger}) + def test_paused_validators_exit_bus_cant_submit_report(stranger): chain.sleep(2 * 24 * 3600) chain.mine() @@ -405,9 +433,10 @@ def test_paused_validators_exit_bus_cant_submit_report(stranger): pause_validators_exit_bus(stranger) report, member = prepare_report() - with brownie.reverts(encode_error('ResumedExpected()')): + with brownie.reverts(encode_error("ResumedExpected()")): contracts.validators_exit_bus_oracle.submitReportData(report, contract_version, {"from": member}) + def test_stopped_lido_can_exit_validators(stranger): chain.sleep(2 * 24 * 3600) chain.mine() diff --git a/tests/regression/test_validator_exit_bus_happy_path.py b/tests/regression/test_validator_exit_bus_happy_path.py index 737f7d4d..38b7820d 100644 --- a/tests/regression/test_validator_exit_bus_happy_path.py +++ b/tests/regression/test_validator_exit_bus_happy_path.py @@ -5,7 +5,9 @@ from utils.config import contracts from utils.test.exit_bus_data import LidoValidator from utils.test.oracle_report_helpers import ( - wait_to_next_available_report_time, reach_consensus, prepare_exit_bus_report + wait_to_next_available_report_time, + reach_consensus, + prepare_exit_bus_report, ) @@ -19,17 +21,20 @@ class ProcessingState: requests_count: int requests_submitted: int + 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() return ref_slot + def send_report_with_consensus(ref_slot, report, report_hash): consensus_version = contracts.validators_exit_bus_oracle.getConsensusVersion() contract_version = contracts.validators_exit_bus_oracle.getContractVersion() - submitter = reach_consensus(ref_slot, report_hash, consensus_version, - contracts.hash_consensus_for_validators_exit_bus_oracle) + submitter = reach_consensus( + ref_slot, report_hash, consensus_version, contracts.hash_consensus_for_validators_exit_bus_oracle + ) return contracts.validators_exit_bus_oracle.submitReportData(report, contract_version, {"from": submitter}) @@ -52,8 +57,8 @@ def test_send_zero_validators_to_exit(helpers): processing_state_after = ProcessingState(*contracts.validators_exit_bus_oracle.getProcessingState()) # Asserts - helpers.assert_single_event_named('ProcessingStarted', tx, {"refSlot": ref_slot, "hash": report_hash_hex}) - helpers.assert_event_not_emitted('ValidatorExitRequest', tx) + helpers.assert_single_event_named("ProcessingStarted", tx, {"refSlot": ref_slot, "hash": report_hash_hex}) + helpers.assert_event_not_emitted("ValidatorExitRequest", tx) assert total_requests_after == total_requests_before @@ -83,7 +88,8 @@ def test_send_validator_to_exit(helpers, web3): last_processing_ref_slot_before = contracts.validators_exit_bus_oracle.getLastProcessingRefSlot() processing_state_before = ProcessingState(*contracts.validators_exit_bus_oracle.getProcessingState()) last_requested_validator_index_before = contracts.validators_exit_bus_oracle.getLastRequestedValidatorIndices( - module_id, [no_id]) + module_id, [no_id] + ) tx = send_report_with_consensus(ref_slot, report, report_hash) @@ -92,16 +98,22 @@ def test_send_validator_to_exit(helpers, web3): last_processing_ref_slot_after = contracts.validators_exit_bus_oracle.getLastProcessingRefSlot() processing_state_after = ProcessingState(*contracts.validators_exit_bus_oracle.getProcessingState()) last_requested_validator_index_after = contracts.validators_exit_bus_oracle.getLastRequestedValidatorIndices( - module_id, [no_id]) + module_id, [no_id] + ) # Asserts - helpers.assert_single_event_named('ProcessingStarted', tx, {"refSlot": ref_slot, "hash": report_hash_hex}) - helpers.assert_single_event_named('ValidatorExitRequest', tx, - {"stakingModuleId": module_id, - "nodeOperatorId": no_id, - "validatorIndex": validator_id, - "validatorPubkey": validator_key, - "timestamp": web3.eth.get_block(web3.eth.block_number).timestamp}) + helpers.assert_single_event_named("ProcessingStarted", tx, {"refSlot": ref_slot, "hash": report_hash_hex}) + helpers.assert_single_event_named( + "ValidatorExitRequest", + tx, + { + "stakingModuleId": module_id, + "nodeOperatorId": no_id, + "validatorIndex": validator_id, + "validatorPubkey": validator_key, + "timestamp": web3.eth.get_block(web3.eth.block_number).timestamp, + }, + ) assert total_requests_after == total_requests_before + 1 @@ -122,8 +134,8 @@ def test_send_multiple_validators_to_exit(helpers, web3): """ The same as test above but with multiple validators on different node operators """ - first_no_global_index = (first_module_id, first_no_id) = (1, 1) - second_no_global_index = (second_module_id, second_no_id) = (1, 2) + first_no_global_index = (first_module_id, first_no_id) = (1, 7) + second_no_global_index = (second_module_id, second_no_id) = (1, 8) first_validator_id = 2 second_validator_id = 3 first_validator_key = contracts.node_operators_registry.getSigningKey(first_no_id, first_validator_id)[0] @@ -133,7 +145,8 @@ def test_send_multiple_validators_to_exit(helpers, web3): ref_slot = _wait_for_next_ref_slot() report, report_hash = prepare_exit_bus_report( - [(first_no_global_index, first_validator), (second_no_global_index, second_validator)], ref_slot) + [(first_no_global_index, first_validator), (second_no_global_index, second_validator)], ref_slot + ) report_hash_hex = HexString(report_hash, "bytes") # Collect state before @@ -141,9 +154,11 @@ def test_send_multiple_validators_to_exit(helpers, web3): last_processing_ref_slot_before = contracts.validators_exit_bus_oracle.getLastProcessingRefSlot() processing_state_before = ProcessingState(*contracts.validators_exit_bus_oracle.getProcessingState()) first_last_requested_validator_index_before = contracts.validators_exit_bus_oracle.getLastRequestedValidatorIndices( - first_module_id, [first_no_id]) - second_last_requested_validator_index_before = contracts.validators_exit_bus_oracle.getLastRequestedValidatorIndices( - second_module_id, [second_no_id]) + first_module_id, [first_no_id] + ) + second_last_requested_validator_index_before = ( + contracts.validators_exit_bus_oracle.getLastRequestedValidatorIndices(second_module_id, [second_no_id]) + ) tx = send_report_with_consensus(ref_slot, report, report_hash) @@ -152,24 +167,30 @@ def test_send_multiple_validators_to_exit(helpers, web3): last_processing_ref_slot_after = contracts.validators_exit_bus_oracle.getLastProcessingRefSlot() processing_state_after = ProcessingState(*contracts.validators_exit_bus_oracle.getProcessingState()) first_last_requested_validator_index_after = contracts.validators_exit_bus_oracle.getLastRequestedValidatorIndices( - first_module_id, [first_no_id]) + first_module_id, [first_no_id] + ) second_last_requested_validator_index_after = contracts.validators_exit_bus_oracle.getLastRequestedValidatorIndices( - second_module_id, [second_no_id]) + second_module_id, [second_no_id] + ) # Asserts - helpers.assert_single_event_named('ProcessingStarted', tx, {"refSlot": ref_slot, "hash": report_hash_hex}) + helpers.assert_single_event_named("ProcessingStarted", tx, {"refSlot": ref_slot, "hash": report_hash_hex}) events = helpers.filter_events_from(tx.receiver, tx.events["ValidatorExitRequest"]) assert len(events) == 2 - assert dict(events[0]) == {"stakingModuleId": first_module_id, - "nodeOperatorId": first_no_id, - "validatorIndex": first_validator_id, - "validatorPubkey": first_validator_key, - "timestamp": web3.eth.get_block(web3.eth.block_number).timestamp} - assert dict(events[1]) == {"stakingModuleId": second_module_id, - "nodeOperatorId": second_no_id, - "validatorIndex": second_validator_id, - "validatorPubkey": second_validator_key, - "timestamp": web3.eth.get_block(web3.eth.block_number).timestamp} + assert dict(events[0]) == { + "stakingModuleId": first_module_id, + "nodeOperatorId": first_no_id, + "validatorIndex": first_validator_id, + "validatorPubkey": first_validator_key, + "timestamp": web3.eth.get_block(web3.eth.block_number).timestamp, + } + assert dict(events[1]) == { + "stakingModuleId": second_module_id, + "nodeOperatorId": second_no_id, + "validatorIndex": second_validator_id, + "validatorPubkey": second_validator_key, + "timestamp": web3.eth.get_block(web3.eth.block_number).timestamp, + } assert total_requests_after == total_requests_before + 2