From 1eb7f5b625a1fe882d33273c41df31a3467750b6 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sun, 7 Dec 2025 11:10:54 +0000 Subject: [PATCH 1/7] v1_dg1 --- .github/workflows/core_tests.yml | 2 +- .../workflows/dual_governance_regression.yml | 2 +- configs/config_mainnet.py | 8 +- scripts/vote_2025_12_10.py | 348 ----- .../test_node_operators_registry.py | 2 +- tests/test_2025_12_10.py | 81 +- tests/utils_test_2025_12_10_operations.py | 1244 ----------------- utils/test/governance_helpers.py | 45 +- 8 files changed, 37 insertions(+), 1695 deletions(-) delete mode 100644 scripts/vote_2025_12_10.py delete mode 100644 tests/utils_test_2025_12_10_operations.py diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index a702ede19..502bc8d05 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -9,7 +9,7 @@ on: - "feat/rc2" - "feat/rc1" - "feat/next-vote" - - "feat/v3-vote" + - "v1_dg1" schedule: - cron: "0 0 * * TUE" diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index 2b7656567..2fb12be08 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" + - "v1_dg1" workflow_dispatch: jobs: diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 4d40b52b4..985b6712e 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 = 350 -CURATED_STAKING_MODULE_TREASURY_FEE_BP = 650 +CURATED_STAKING_MODULE_MODULE_FEE_BP = 500 +CURATED_STAKING_MODULE_TREASURY_FEE_BP = 500 CURATED_STAKING_MODULE_ID = 1 CURATED_STAKING_MODULE_NAME = "curated-onchain-v1" CURATED_STAKING_MODULE_TYPE = ( @@ -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 @@ -172,7 +172,7 @@ "0x637572617465642d6f6e636861696e2d76310000000000000000000000000000" ) SIMPLE_DVT_VERSION = 4 -SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 478 +SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 440 SIMPLE_DVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SIMPLE_DVT_MODULE_MIN_DEPOSITS_BLOCK_DISTANCE = 25 diff --git a/scripts/vote_2025_12_10.py b/scripts/vote_2025_12_10.py deleted file mode 100644 index 99b7354dc..000000000 --- a/scripts/vote_2025_12_10.py +++ /dev/null @@ -1,348 +0,0 @@ -""" -# Vote 2025_12_10 - -=== 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 - -II. Raise SDVT module stake share limit -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 - -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 -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 -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}. -""" - -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 -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 utils.allowed_recipients_registry import set_limit_parameters - - -# ============================== Types =================================== -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" -STABLECOINS_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" - - -# ============================== Tokens =================================== -SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" -USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" -USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" -DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" -LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" -STETH_TOKEN = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - - -# ============================== Constants =================================== -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 - -SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -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 -SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 - -TRP_PERIOD_DURATION_MONTHS = 12 -TRP_NEW_LIMIT = 15_000_000 * 10**18 - -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) - 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)) - 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 ================================== -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 ====================================== -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) - - 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 - ( - 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 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( - SDVT_MODULE_ID, - SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_NEW_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.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), - ) - ]), - agent_forward([ - # 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, - registry_address=ET_TRP_REGISTRY, - ), - ]), - ] - - 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", - dg_call_script[0] - ), - ( - "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)), - VOTING, - ) - ), - ), - ( - "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)) - ), - ( - "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)), - VOTING, - ) - ) - ), - ( - "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, - revoke_from=ET_EVM_SCRIPT_EXECUTOR, - ), - ), - ( - "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, - grant_to=ET_EVM_SCRIPT_EXECUTOR, - params=amount_limits(), - ), - ), - ( - "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, - 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/tests/acceptance/test_node_operators_registry.py b/tests/acceptance/test_node_operators_registry.py index c9f4ac0d0..d05bdf7c6 100644 --- a/tests/acceptance/test_node_operators_registry.py +++ b/tests/acceptance/test_node_operators_registry.py @@ -115,7 +115,7 @@ def test_nor_state(contract): 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 - soft_limit_0_node_operators = [32] # NO id 32 was added on vote 10-12-25 + soft_limit_0_node_operators = [] # 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 diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index 791b2afdb..c6ae86890 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -1,92 +1,15 @@ import pytest import brownie import tests.utils_test_2025_12_10_lidov3 as lidov3 -import tests.utils_test_2025_12_10_operations as ops @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): +def test_vote_v1_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, 194, 6, ) - ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, - 195, 7, - ) - - lidov3.enact_and_test_dg(stranger, 6) - - ops.enact_and_test_dg(stranger, 7) - -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, - 194, 6, - ) - - ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, - 195, 7, - ) - - ops.enact_and_test_dg(stranger, 7) - - lidov3.enact_and_test_dg(stranger, 6) - -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, - 194, 6, - ) - - lidov3.enact_and_test_dg(stranger, 6) - - ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, - 195, 7, - ) - - ops.enact_and_test_dg(stranger, 7) - -def test_vote_v2_v1_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - - 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, - ) - - lidov3.enact_and_test_dg(stranger, 7) - - ops.enact_and_test_dg(stranger, 6) - -def test_vote_v2_v1_dg2_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - - 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, - ) - - ops.enact_and_test_dg(stranger, 6) - - lidov3.enact_and_test_dg(stranger, 7) - -def test_vote_v2_dg2_v1_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): - - ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, - 194, 6, - ) - - ops.enact_and_test_dg(stranger, 6) - - lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, - 195, 7, - ) - - lidov3.enact_and_test_dg(stranger, 7) \ No newline at end of file + lidov3.enact_and_test_dg(stranger, 6) \ No newline at end of file diff --git a/tests/utils_test_2025_12_10_operations.py b/tests/utils_test_2025_12_10_operations.py deleted file mode 100644 index 199ed4442..000000000 --- a/tests/utils_test_2025_12_10_operations.py +++ /dev/null @@ -1,1244 +0,0 @@ -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 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, -) -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" - -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" -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_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 -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_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 -TRP_PERIOD_DURATION_MONTHS = 12 - -ALLOWED_TOKENS_BEFORE = 3 -ALLOWED_TOKENS_AFTER = 4 - -A41_NO_ID = 32 -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_MODE_AFTER, -) - - -# ============================== 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_NEW_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_MODE_AFTER, NEW_A41_TARGET_LIMIT), - ) - ]), - 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 = "bafkreieptki4mrkhpd22ij3cym777l4iivrdknwtglchdtvptujz2dgn7u" - - # ======================================================================= - # ========================= 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(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(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 - 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, set 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 - 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) - 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 = 4 - - # ======================================================================= - # ========================= 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_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 - 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_MODE_BEFORE - assert a41_summary_before['depositableValidatorsCount'] > 0 - assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" - - # 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 - 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_NEW_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_limit_parameter_event( - dg_events[3], - 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_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 - 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 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_MODE_AFTER - assert a41_summary_after['depositableValidatorsCount'] == 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: - 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 - 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" diff --git a/utils/test/governance_helpers.py b/utils/test/governance_helpers.py index da4061c6d..ffec2e6bd 100644 --- a/utils/test/governance_helpers.py +++ b/utils/test/governance_helpers.py @@ -29,20 +29,31 @@ def execute_vote(helpers, vote_ids_from_env): 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() - 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) + sequence_key = "v1_dg1" + print("ACTION_SEQUENCE:", sequence_key) + + state = { + "v1_ids": None, + "v2_ids": None, + } + + def vote(id): + proposals_count_before1 = contracts.emergency_protected_timelock.getProposalsCount() + start_and_execute_votes(contracts.voting, helpers, id) + proposals_count_after1 = contracts.emergency_protected_timelock.getProposalsCount() + state[f"v{id+1}_ids"] = list(range(proposals_count_before1 + 1, proposals_count_after1 + 1)) + + def dual_governance(ids): + process_proposals(ids) + + actions = { + "v1": lambda: vote(0), + "dg1": lambda: dual_governance(state["v1_ids"]), + "v2": lambda: vote(1), + "dg2": lambda: dual_governance(state["v2_ids"]), + } + + steps = sequence_key.split("_") + for action in steps: + actions[action]() + From 69f00cf703df18411e76f3d8b1cc31643c777752 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sun, 7 Dec 2025 11:16:19 +0000 Subject: [PATCH 2/7] fix reset --- tests/test_2025_12_10.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/test_2025_12_10.py b/tests/test_2025_12_10.py index c6ae86890..058f30d6a 100644 --- a/tests/test_2025_12_10.py +++ b/tests/test_2025_12_10.py @@ -1,10 +1,5 @@ -import pytest -import brownie import tests.utils_test_2025_12_10_lidov3 as lidov3 -@pytest.fixture(autouse=True) -def isolation(): - brownie.chain.reset() def test_vote_v1_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): From 2acb827805f9ef0e081f6e2f5d9daa14ac521f87 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sun, 7 Dec 2025 13:16:08 +0100 Subject: [PATCH 3/7] Update SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD --- 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 985b6712e..4ca05cd46 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -172,7 +172,7 @@ "0x637572617465642d6f6e636861696e2d76310000000000000000000000000000" ) SIMPLE_DVT_VERSION = 4 -SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 440 +SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 444 SIMPLE_DVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SIMPLE_DVT_MODULE_MIN_DEPOSITS_BLOCK_DISTANCE = 25 From 663dc26bf67c3cb663d973533559ad7965dc3031 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Sun, 7 Dec 2025 13:16:10 +0100 Subject: [PATCH 4/7] Fix expected vote length in snapshot test --- tests/snapshot/test_voting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/snapshot/test_voting.py b/tests/snapshot/test_voting.py index d6ea0d7ba..905bfeaba 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 + 3)} + step_name, diff, {"votesLength": ValueChanged(from_val=votesLength + 1, to_val=votesLength + 2)} ) assert_no_diffs(step_name, diff) From 92817b98acebdff1fa8eb198b33209aede4afa8a Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 18:41:28 +0000 Subject: [PATCH 5/7] fix vote name --- tests/test_2025_12_15.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_2025_12_15.py b/tests/test_2025_12_15.py index 4dc69dfe0..e0e405141 100644 --- a/tests/test_2025_12_15.py +++ b/tests/test_2025_12_15.py @@ -1,4 +1,4 @@ -import tests.utils_test_2025_12_10_lidov3 as lidov3 +import tests.utils_test_2025_12_15_lidov3 as lidov3 def test_vote_v1_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): From 2ab02e060e0be1f417cf0a559f9066079b257d06 Mon Sep 17 00:00:00 2001 From: Nikita P Date: Wed, 10 Dec 2025 18:47:02 +0000 Subject: [PATCH 6/7] fix vote name --- scripts/vote_2025_12_15.py | 348 ------ tests/utils_test_2025_12_15_operations.py | 1238 --------------------- 2 files changed, 1586 deletions(-) delete mode 100644 scripts/vote_2025_12_15.py delete mode 100644 tests/utils_test_2025_12_15_operations.py diff --git a/scripts/vote_2025_12_15.py b/scripts/vote_2025_12_15.py deleted file mode 100644 index bf69b8096..000000000 --- a/scripts/vote_2025_12_15.py +++ /dev/null @@ -1,348 +0,0 @@ -""" -# Vote 2025_12_15 - -=== 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 - -II. Raise SDVT module stake share limit -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 - -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 -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 -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}. -""" - -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 -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 utils.allowed_recipients_registry import set_limit_parameters - - -# ============================== Types =================================== -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" -STABLECOINS_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" - - -# ============================== Tokens =================================== -SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" -USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" -USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" -DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" -LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" -STETH_TOKEN = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - - -# ============================== Constants =================================== -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 - -SDVT_MODULE_ID = 2 -SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 -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 -SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 - -TRP_PERIOD_DURATION_MONTHS = 12 -TRP_NEW_LIMIT = 15_000_000 * 10**18 - -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) - 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)) - 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 ================================== -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 ====================================== -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) - - 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 - ( - 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 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( - SDVT_MODULE_ID, - SDVT_MODULE_NEW_TARGET_SHARE_BP, - SDVT_MODULE_NEW_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.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), - ) - ]), - agent_forward([ - # 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, - registry_address=ET_TRP_REGISTRY, - ), - ]), - ] - - 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", - dg_call_script[0] - ), - ( - "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)), - VOTING, - ) - ), - ), - ( - "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)) - ), - ( - "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)), - VOTING, - ) - ) - ), - ( - "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, - revoke_from=ET_EVM_SCRIPT_EXECUTOR, - ), - ), - ( - "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, - grant_to=ET_EVM_SCRIPT_EXECUTOR, - params=amount_limits(), - ), - ), - ( - "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, - 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/tests/utils_test_2025_12_15_operations.py b/tests/utils_test_2025_12_15_operations.py deleted file mode 100644 index 10ba176f3..000000000 --- a/tests/utils_test_2025_12_15_operations.py +++ /dev/null @@ -1,1238 +0,0 @@ -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 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, -) -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_15 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" - -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" -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_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 -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_420 * 10**15 # == 9_178_284.42 * 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 - -ALLOWED_TOKENS_BEFORE = 3 -ALLOWED_TOKENS_AFTER = 4 - -A41_NO_ID = 32 -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_MODE_AFTER, -) - - -# ============================== 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_NEW_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_MODE_AFTER, NEW_A41_TARGET_LIMIT), - ) - ]), - 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 = "bafkreieptki4mrkhpd22ij3cym777l4iivrdknwtglchdtvptujz2dgn7u" - - # ======================================================================= - # ========================= 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(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(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 - 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, set 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 - 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) - 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 = 4 - - # ======================================================================= - # ========================= 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 - TRP_ALREADY_SPENT_AFTER = 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_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 - 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_MODE_BEFORE - assert a41_summary_before['depositableValidatorsCount'] > 0 - assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" - - # 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 - 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 - - 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_NEW_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_limit_parameter_event( - dg_events[3], - 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_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 - 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 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_MODE_AFTER - assert a41_summary_after['depositableValidatorsCount'] == 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: - 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 - 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 - 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 - - # 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 = 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() - - # 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 = [] - 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 d83ee78ca2cf39e826893d7161a99d2854044aec Mon Sep 17 00:00:00 2001 From: Nikita P Date: Fri, 12 Dec 2025 12:23:22 +0000 Subject: [PATCH 7/7] rem ops test file --- tests/utils_test_2025_12_15_operations.py | 1229 --------------------- 1 file changed, 1229 deletions(-) delete mode 100644 tests/utils_test_2025_12_15_operations.py diff --git a/tests/utils_test_2025_12_15_operations.py b/tests/utils_test_2025_12_15_operations.py deleted file mode 100644 index aae9f18c7..000000000 --- a/tests/utils_test_2025_12_15_operations.py +++ /dev/null @@ -1,1229 +0,0 @@ -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 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, -) -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_15 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" - -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" -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_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 -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_420 * 10**15 # == 9_178_284.42 * 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 - -ALLOWED_TOKENS_BEFORE = 3 -ALLOWED_TOKENS_AFTER = 4 - -A41_NO_ID = 32 -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_MODE_AFTER, -) - - -# ============================== 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_NEW_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_MODE_AFTER, NEW_A41_TARGET_LIMIT), - ) - ]), - 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 = "bafkreieptki4mrkhpd22ij3cym777l4iivrdknwtglchdtvptujz2dgn7u" - - # ======================================================================= - # ========================= 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(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(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 - 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, set 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).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) - 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 = 4 - - # ======================================================================= - # ========================= 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 - TRP_ALREADY_SPENT_AFTER = 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_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 - 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_MODE_BEFORE - assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" - - # 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 - 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 - - 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_NEW_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_limit_parameter_event( - dg_events[3], - 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_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 - 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 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_MODE_AFTER - assert a41_summary_after['depositableValidatorsCount'] == 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: - 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 - 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 - 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 - - # 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 = 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() - - # sleep to January so that TRP 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 = [] - 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() - - # 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 = [] - 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"