├── .editorconfig ├── .gitattributes ├── .github ├── CODEOWNERS ├── actions │ └── brownie_fork_tests │ │ └── action.yml └── workflows │ ├── build_and_push_image.yml │ ├── large_vote_ci.yml │ └── normal_vote_ci.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile ├── README.md ├── archive ├── scripts │ ├── add_node_operators.py │ ├── before_pectra_upgrade_holesky.py │ ├── calldata_voting.py │ ├── check_refunds.py │ ├── check_withdrawal_credentials_change.py │ ├── deploy_rewards_manager_tokens_recoverer.py │ ├── dual_governance_upgrade_holesky.py │ ├── fallback_2024_10_08_enable_deposits.py │ ├── fallback_2024_10_08_rollback_l1.py │ ├── fork_utils_2024_10_08.py │ ├── increase_staking_limits_with_refund.py │ ├── revert_upgrade_2024_08_20.py │ ├── revert_vote_2022_06_21.py │ ├── set_lido_locator_implementation.py │ ├── set_node_operators_limit.py │ ├── set_withdrawal_credentials.py │ ├── setup_easytrack_limits.py │ ├── shapella_deploy_template.py │ ├── transfer_locator_ownership.py │ ├── upgrade_2022_06_21.py │ ├── upgrade_2024_08_13.py │ ├── upgrade_2024_08_20.py │ ├── upgrade_2024_10_08.py │ ├── upgrade_2024_10_22.py │ ├── upgrade_shapella.py │ ├── upgrade_shapella_1_goerli.py │ ├── upgrade_shapella_2_revoke_roles_goerli.py │ ├── upgrade_voting_delegation_holesky.py │ ├── vote_2021_05_omnibus_rewards_limits.py │ ├── vote_2021_06_omnibus_revoke_permission_and_increase_staking_limits.py │ ├── vote_2021_07_22_cover_refund.py │ ├── vote_2021_07_29_fund_easy_audit_and_referral_payout.py │ ├── vote_2021_08_05_fund_deversifi_and_curve_rewards.py │ ├── vote_2021_08_12_fund_sushi_lp_rewards_increase_no_limits.py │ ├── vote_2021_08_19_fund_deversifi_lexpunk_and_balancer_rewards.py │ ├── vote_2021_08_26_nor_ops_1m_nft_lego_ssv.py │ ├── vote_2021_09_02.py │ ├── vote_2021_09_09.py │ ├── vote_2021_09_16.py │ ├── vote_2021_09_23.py │ ├── vote_2021_09_30.py │ ├── vote_2021_10_05.py │ ├── vote_2021_10_07.py │ ├── vote_2021_10_14.py │ ├── vote_2021_10_21.py │ ├── vote_2021_10_28.py │ ├── vote_2021_11_04.py │ ├── vote_2021_11_11.py │ ├── vote_2021_11_18.py │ ├── vote_2021_11_25.py │ ├── vote_2021_11_26.py │ ├── vote_2021_12_02.py │ ├── vote_2021_12_09.py │ ├── vote_2021_12_16.py │ ├── vote_2021_12_23.py │ ├── vote_2021_12_27.py │ ├── vote_2022_01_13.py │ ├── vote_2022_01_20.py │ ├── vote_2022_02_03.py │ ├── vote_2022_02_04.py │ ├── vote_2022_02_07.py │ ├── vote_2022_02_17.py │ ├── vote_2022_03_03.py │ ├── vote_2022_03_17.py │ ├── vote_2022_03_22.py │ ├── vote_2022_03_24.py │ ├── vote_2022_04_07.py │ ├── vote_2022_04_12.py │ ├── vote_2022_04_16.py │ ├── vote_2022_04_19.py │ ├── vote_2022_04_26.py │ ├── vote_2022_05_03.py │ ├── vote_2022_05_17.py │ ├── vote_2022_05_24.py │ ├── vote_2022_05_31.py │ ├── vote_2022_06_14.py │ ├── vote_2022_06_14_1_goerli_ipfs.py │ ├── vote_2022_06_14_2_goerli_ipfs.py │ ├── vote_2022_06_21_NOs_onb.py │ ├── vote_2022_06_21_goerli_1.py │ ├── vote_2022_06_21_goerli_2.py │ ├── vote_2022_06_21_goerli_3.py │ ├── vote_2022_06_21_goerli_lido_app.py │ ├── vote_2022_07_12.py │ ├── vote_2022_08_09.py │ ├── vote_2022_08_16.py │ ├── vote_2022_10_04.py │ ├── vote_2022_10_18.py │ ├── vote_2022_10_25.py │ ├── vote_2022_11_10.py │ ├── vote_2022_11_15.py │ ├── vote_2022_11_22.py │ ├── vote_2022_12_06_1.py │ ├── vote_2022_12_06_2.py │ ├── vote_2022_12_13.py │ ├── vote_2023_01_10.py │ ├── vote_2023_01_24.py │ ├── vote_2023_01_31.py │ ├── vote_2023_01_31_goerli.py │ ├── vote_2023_02_21.py │ ├── vote_2023_02_21_goerli.py │ ├── vote_2023_04_18.py │ ├── vote_2023_04_25.py │ ├── vote_2023_05_03_goerli.py │ ├── vote_2023_05_23.py │ ├── vote_2023_05_23_cancelled.py │ ├── vote_2023_06_20.py │ ├── vote_2023_06_20_2.py │ ├── vote_2023_06_20_goerli.py │ ├── vote_2023_08_08.py │ ├── vote_2023_08_08_goerli.py │ ├── vote_2023_08_15.py │ ├── vote_2023_09_12.py │ ├── vote_2023_10_03.py │ ├── vote_2023_10_31.py │ ├── vote_2023_11_07.py │ ├── vote_2023_12_12.py │ ├── vote_2023_12_18.py │ ├── vote_2024_01_16.py │ ├── vote_2024_01_23.py │ ├── vote_2024_02_23.py │ ├── vote_2024_03_19.py │ ├── vote_2024_04_23.py │ ├── vote_2024_07_23.py │ ├── vote_2024_08_02_sepolia.py │ ├── vote_2024_11_26_holesky.py │ ├── vote_2024_12_17.py │ ├── vote_2025_01_28.py │ ├── vote_2025_03_18.py │ ├── vote_2025_04_03_holesky.py │ ├── vote_2025_04_08_hoodi.py │ ├── vote_2025_05_08_hoodi_dg_launch.py │ ├── vote_2025_05_11.py │ ├── vote_2025_05_14_hoodi.py │ ├── vote_2025_05_27.py │ ├── vote_23_04_2025.py │ ├── vote_goerli_fix_dai_limit.py │ └── vote_sr_v2_holesky.py └── tests │ ├── _test_fallback_2024_10_08_enable_deposits.py │ ├── _test_fallback_2024_10_08_rollback_l1.py │ ├── permission_parameters.py │ ├── shapella_acceptance_goerli │ ├── conftest.py │ ├── test_state.py │ └── test_state_2.py │ ├── shapella_upgrade_acceptance │ ├── __init__.py │ ├── conftest.py │ ├── test_accounting_oracle.py │ ├── test_accounting_oracle_negative.py │ ├── test_burner.py │ ├── test_dsm.py │ ├── test_el_rewards_vault.py │ ├── test_gate_seal_acceptance.py │ ├── test_legacy_oracle.py │ ├── test_lido.py │ ├── test_locator.py │ ├── test_node_operators_registry.py │ ├── test_oracle_daemon_config.py │ ├── test_oracle_report_sanity_checker.py │ ├── test_permissions_aragon.py │ ├── test_staking_router.py │ ├── test_veb_negative.py │ ├── test_veb_oracle.py │ ├── test_withdrawal_queue.py │ ├── test_withdrawal_vault.py │ └── test_withdrawals_negative.py │ ├── shapella_upgrade_preliminary │ ├── test_no_debug_address_in_prod.py │ ├── test_petrification.py │ ├── test_precondition_addresses.py │ └── test_template_permissions.py │ ├── shapella_upgrade_snapshot │ ├── __init__.py │ ├── test_dsm_snapshot.py │ ├── test_first_slots.py │ ├── test_legacy_oracle_snapshot.py │ ├── test_lido_snapshot.py │ ├── test_snapshot_node_operators_registry.py │ ├── test_snapshot_plain_submit.py │ ├── test_snapshot_voting.py │ └── utils.py │ ├── test_2022_06_21.py │ ├── test_2022_06_21_2_goerli_lido_app.py │ ├── test_2022_06_21_NOs_onb.py │ ├── test_2022_06_21_no_auto_execution.py │ ├── test_2022_06_21_votes_ordering.py │ ├── test_2022_07_12.py │ ├── test_2022_08_09.py │ ├── test_2022_08_16.py │ ├── test_2022_10_04.py │ ├── test_2022_10_18.py │ ├── test_2022_10_25.py │ ├── test_2022_11_10.py │ ├── test_2022_11_15.py │ ├── test_2022_11_22.py │ ├── test_2023_01_10.py │ ├── test_2023_01_24.py │ ├── test_2023_01_31.py │ ├── test_2023_01_31_goerli.py │ ├── test_2023_02_21.py │ ├── test_2023_02_21_goerli.py │ ├── test_2023_04_18.py │ ├── test_2023_04_25.py │ ├── test_2023_05_03_goerli.py │ ├── test_2023_05_23.py │ ├── test_2023_05_23_cancelled.py │ ├── test_2023_06_20.py │ ├── test_2023_06_20_2.py │ ├── test_2023_06_20_coverage_application.py │ ├── test_2023_06_20_goerli.py │ ├── test_2023_08_08.py │ ├── test_2023_08_08_coverage_application.py │ ├── test_2023_08_08_goerli.py │ ├── test_2023_08_15.py │ ├── test_2023_09_12.py │ ├── test_2023_10_03.py │ ├── test_2023_10_31.py │ ├── test_2023_11_07.py │ ├── test_2023_12_12.py │ ├── test_2023_12_18.py │ ├── test_2024_01_16.py │ ├── test_2024_01_23.py │ ├── test_2024_02_23.py │ ├── test_2024_03_19.py │ ├── test_2024_04_23.py │ ├── test_2024_07_23.py │ ├── test_2024_08_02_sepolia.py │ ├── test_2024_10_08.py │ ├── test_2024_12_17.py │ ├── test_2025_03_18.py │ ├── test_2025_04_03_holesky.py │ ├── test_2025_05_08_hoodi_dg_launch.py │ ├── test_2025_05_11.py │ ├── test_2025_05_14_hoodi.py │ ├── test_2025_05_27.py │ ├── test_23_04_2025.py │ ├── test_before_pectra_upgrade_holesky.py │ ├── test_delegation_deployed_trp_recipients.py │ ├── test_dual_governance_upgrade_holesky.py │ ├── test_legacy_oracle_skipped_report.py │ ├── test_oracle_report_under_heavy_mev.py │ ├── test_revert_2022_06_21.py │ ├── test_revert_upgrade_2024_08_20.py │ ├── test_upgrade_2024_08_13.py │ ├── test_upgrade_2024_08_20.py │ ├── test_upgrade_2024_10_22.py │ ├── test_upgrade_shapella.py │ ├── test_upgrade_shapella_goerli.py │ ├── test_upgrade_template.py │ ├── test_upgrade_voting_delegation_holesky.py │ ├── test_vote_2022_12_06_1.py │ ├── test_vote_2022_12_06_2.py │ ├── test_vote_2022_12_13.py │ ├── test_vote_2025_01_28.py │ ├── test_vote_2025_04_08_hoodi.py │ ├── test_vote_sr_v2_holesky.py │ ├── test_voting_reverse_upgrade.py │ ├── xtest_2021_07_22_cover_refund.py │ ├── xtest_2021_07_29_fund_easy_audit_and_referral_payout.py │ ├── xtest_2021_08_05_fund_deversifi_and_curve_rewards.py │ ├── xtest_2021_08_12_fund_sushi_lp_rewards_increase_no_limits.py │ ├── xtest_2021_08_26_nor_ops_1m_nft_lego_ssv.py │ ├── xtest_2021_09_02.py │ ├── xtest_2021_09_09.py │ ├── xtest_2021_09_16.py │ ├── xtest_2021_09_23.py │ ├── xtest_2021_09_30.py │ ├── xtest_2021_10_05.py │ ├── xtest_2021_10_07.py │ ├── xtest_2021_10_14.py │ ├── xtest_2021_10_21.py │ ├── xtest_2021_10_28.py │ ├── xtest_2021_11_04.py │ ├── xtest_2021_11_11.py │ ├── xtest_2021_11_18.py │ ├── xtest_2021_11_25.py │ ├── xtest_2021_11_26.py │ ├── xtest_2021_12_02.py │ ├── xtest_2021_12_09.py │ ├── xtest_2021_12_16.py │ ├── xtest_2021_12_23.py │ ├── xtest_2021_12_27.py │ ├── xtest_2022_01_13.py │ ├── xtest_2022_01_20.py │ ├── xtest_2022_02_03.py │ ├── xtest_2022_02_04.py │ ├── xtest_2022_02_07.py │ ├── xtest_2022_02_17.py │ ├── xtest_2022_03_03.py │ ├── xtest_2022_03_17.py │ ├── xtest_2022_03_22.py │ ├── xtest_2022_03_24.py │ ├── xtest_2022_04_07.py │ ├── xtest_2022_04_12.py │ ├── xtest_2022_04_16.py │ ├── xtest_2022_04_19.py │ ├── xtest_2022_04_26.py │ ├── xtest_2022_05_03.py │ ├── xtest_2022_05_17.py │ ├── xtest_2022_05_24.py │ ├── xtest_2022_05_24_burn_shares.py │ ├── xtest_2022_05_24_el_rewards_tests.py │ ├── xtest_2022_05_24_keys_op_index.py │ ├── xtest_2022_05_24_pause_role_tests.py │ ├── xtest_2022_05_24_snapshot_rewards_distribution.py │ ├── xtest_2022_05_24_snapshot_submit.py │ ├── xtest_2022_05_24_snapshot_submit_with_rebase.py │ ├── xtest_2022_05_24_staking_limits.py │ ├── xtest_2022_05_24_transfer_shares_and_oracle_upgrade.py │ ├── xtest_2022_05_31.py │ ├── xtest_2022_05_31_coverage_acceptance.py │ ├── xtest_2022_05_31_permissions.py │ ├── xtest_2022_05_31_snapshot_merge_ready_proto_round2.py │ ├── xtest_2022_05_31_snapshot_rewards_distribution.py │ ├── xtest_2022_05_31_snapshot_submit_with_rebase.py │ ├── xtest_2022_06_14.py │ ├── xtest_2022_06_14_1_goerli_ipfs.py │ ├── xtest_2022_06_14_2_goerli_ipfs.py │ ├── xtest_2022_06_14_dsm.py │ ├── xtest_2022_06_14_permissions.py │ ├── xtest_add_node_operators.py │ ├── xtest_rewards_manager_tokens_recoverer.py │ ├── xtest_set_node_operators_limit.py │ ├── xtest_setup_easytrack_limits.py │ └── xtest_voting_upgrade_snapshot.py ├── assets └── voting.png ├── brownie-config.yml ├── configs ├── config_goerli.py ├── config_holesky.py ├── config_hoodi.py ├── config_mainnet.py └── config_sepolia.py ├── contracts ├── EtherFunder.vy ├── MockCallTarget.vy ├── MockHashConsensus.vy ├── OnchainMetrics.vy ├── OpStackTokenRatePusherWithSomeErrorStub.sol ├── RewardsManagerTokensRecoverer.vy ├── UpgradeableProxy.sol └── shapella-upgrade │ └── ShapellaUpgradeTemplate.sol ├── ganache.sh ├── hardhat.config.js ├── interfaces ├── 0xsplits │ ├── SplitMain.json │ ├── SplitWallet.json │ └── VestingModule.json ├── ACL.json ├── APMRegistry.json ├── AccessControl.json ├── AccessControlEnumerable.json ├── AccountingOracle.json ├── ActivateNodeOperators.json ├── AddAllowedRecipient.json ├── AddNodeOperators.json ├── AddRewardProgram.json ├── Agent.json ├── AllowedRecipientRegistry.json ├── AllowedTokensRegistry.json ├── AnchorVault.json ├── AnchorVaultProxy.json ├── AppProxyUpgradeable.json ├── BalancerReawardsManager.json ├── BaseOracle.json ├── BeaconChainDepositor.json ├── Burner.json ├── CallsScript.json ├── ChangeNodeOperatorManagers.json ├── CurveRewardsManager.json ├── DGAdminExecutor.json ├── DGLaunchOmnibus.json ├── DGLaunchVerifier.json ├── Dai.json ├── DeactivateNodeOperators.json ├── DepositSecurityModule.json ├── DualGovernance.json ├── DualGovernanceConfigProvider.json ├── DualGovernanceExecutor.json ├── EIP712StETH.json ├── ENS.json ├── ERC20BridgedPermit.json ├── EVMScriptRegistry.json ├── EasyTrack.json ├── EmergencyGovernance.json ├── EmergencyProtectedTimelock.json ├── Escrow.json ├── Finance.json ├── GateSeal.json ├── GateSealFactory.json ├── HashConsensus.json ├── IAccountingOracle.json ├── IConsensusContract.json ├── IDepositContract.json ├── IERC2612.json ├── IETHRegistrarController.json ├── IEVMScriptFactory.json ├── IHashConsensus.json ├── IInterfaceResolver.json ├── ILegacyOracle.json ├── ILido.json ├── ILidoExecutionLayerRewardsVault.json ├── INFTDescriptor.json ├── IOracleReportSanityChecker.json ├── IPausable.json ├── IPostTokenRebaseReceiver.json ├── IReportAsyncProcessor.json ├── IStETH.json ├── IStakingRouter.json ├── ITokenRateUpdatable.json ├── IWithdrawalQueue.json ├── IWithdrawalVault.json ├── IWstETH.json ├── IncreaseNodeOperatorStakingLimit.json ├── InsuranceFund.json ├── Kernel.json ├── L1LidoTokensBridge.json ├── L2ERC20ExtendedTokensBridge.json ├── LegacyOracle.json ├── Lido.json ├── LidoExecutionLayerRewardsVault.json ├── LidoLocator.json ├── LidoOracle.json ├── LidoTemplate.json ├── MEVBoostRelayAllowedList.json ├── MiniMeToken.json ├── NodeOperatorsRegistry.json ├── OpBridgeExecutor.json ├── OpCrossDomainMessenger.json ├── OptimismPortal2.json ├── OracleDaemonConfig.json ├── OracleReportSanityChecker.json ├── OssifiableProxy.json ├── PositiveTokenRebaseLimiter.json ├── RemoveAllowedRecipient.json ├── RemoveRewardProgram.json ├── Repo.json ├── RewardProgramsRegistry.json ├── RewardsManager.json ├── RolesValidator.json ├── Sandbox.json ├── SetNodeOperatorNames.json ├── SetNodeOperatorRewardAddresses.json ├── SetVettedValidatorsLimits.json ├── SigningKeys.json ├── SimpleDVT.json ├── StETH.json ├── StETHPermit.json ├── StakingRouter.json ├── TiebreakerCommittee.json ├── TimeConstraints.json ├── TokenManager.json ├── TokenRateNotifier.json ├── TopUpAllowedRecipients.json ├── TopUpRewardPrograms.json ├── UpdateTargetValidatorLimits.json ├── Usdc.json ├── UsdcMasterMinter.json ├── Usdt.json ├── ValidatorsExitBusOracle.json ├── Vault.json ├── Versioned.json ├── VestingEscrowFactory.json ├── Voting.json ├── VotingAdapter.json ├── WethToken.json ├── WithdrawalContractProxy.json ├── WithdrawalQueue.json ├── WithdrawalQueueBase.json ├── WithdrawalQueueERC721.json ├── WithdrawalVault.json ├── WithdrawalVaultManager.json ├── WstETH.json ├── csm │ ├── CSAccounting.json │ ├── CSEarlyAdoption.json │ ├── CSFeeDistributor.json │ ├── CSFeeOracle.json │ ├── CSHashConsensus.json │ ├── CSModule.json │ └── CSVerifier.json ├── obol_split │ ├── ObolLidoSplit.json │ └── ObolLidoSplitFactory.json └── v1 │ ├── DepositSecurityModuleV1.json │ ├── LidoV1.json │ └── NodeOperatorsRegistryV1.json ├── network-config.yaml ├── package.json ├── poetry.lock ├── pyproject.toml ├── tests ├── README.md ├── __init__.py ├── acceptance │ ├── __init__.py │ ├── conftest.py │ ├── test_accounting_oracle.py │ ├── test_accounting_oracle_negative.py │ ├── test_burner.py │ ├── test_csm.py │ ├── test_dsm.py │ ├── test_dual_governance.py │ ├── test_el_rewards_vault.py │ ├── test_gate_seal_acceptance.py │ ├── test_legacy_oracle.py │ ├── test_lido.py │ ├── test_locator.py │ ├── test_node_operators_registry.py │ ├── test_oracle_daemon_config.py │ ├── test_oracle_report_sanity_checker.py │ ├── test_simple_dvt_module.py │ ├── test_staking_router.py │ ├── test_veb_negative.py │ ├── test_veb_oracle.py │ ├── test_withdrawal_queue.py │ ├── test_withdrawal_vault.py │ └── test_withdrawals_negative.py ├── conftest.py ├── internal │ ├── __init__.py │ ├── conftest.py │ ├── test_group_voting_events_from_receipt.py │ └── test_ipfs.py ├── regression │ ├── __init__.py │ ├── conftest.py │ ├── test_accounting.py │ ├── test_accounting_oracle_extra_data.py │ ├── test_accounting_oracle_extra_data_full_items.py │ ├── test_all_round_happy_path.py │ ├── test_burn_shares.py │ ├── test_csm.py │ ├── test_dsm_keys_unvetting.py │ ├── test_dsm_pause_deposits.py │ ├── test_easy_track.py │ ├── test_easy_track_factories.py │ ├── test_el_rewards.py │ ├── test_gate_seal.py │ ├── test_hash_consensus_negative.py │ ├── test_neg_rebase_sanity_checks.py │ ├── test_nft_url.py │ ├── test_node_operators_flow.py │ ├── test_oracle_report_with_notifier.py │ ├── test_pause_resume.py │ ├── test_pause_role_tests.py │ ├── test_permissions.py │ ├── test_sanity_checks.py │ ├── test_sdvt_rewards_happy_path.py │ ├── test_sdvt_splitter_rewards_distribution.py │ ├── test_staking_limits.py │ ├── test_staking_module.py │ ├── test_staking_module_happy_path.py │ ├── test_staking_router_stake_distribution.py │ ├── test_validator_exit_bus_happy_path.py │ ├── test_voting_acc.py │ ├── test_withdrawal_edge_cases.py │ └── test_withdrawal_happy_path.py └── snapshot │ ├── __init__.py │ ├── conftest.py │ ├── test_dsm.py │ ├── test_first_slots.py │ ├── test_legacy_oracle_snapshot.py │ ├── test_lido_snapshot.py │ ├── test_node_operators_registry.py │ ├── test_plain_submit.py │ ├── test_voting.py │ └── utils.py ├── usecase ├── node_operators_management.md └── reward_manager_tokens_recovery.md ├── utils ├── README.md ├── __init__.py ├── agent.py ├── allowed_recipients_registry.py ├── balance.py ├── brownie_prelude.py ├── checksummed_address.py ├── config.py ├── csm.py ├── dsm.py ├── easy_track.py ├── evm_script.py ├── finance.py ├── import_current_votes.py ├── ipfs.py ├── kernel.py ├── mainnet_fork.py ├── node_operators.py ├── oracle.py ├── permission_parameters.py ├── permissions.py ├── repo.py ├── shapella_upgrade.py ├── splits_config.py ├── staking_module.py ├── test │ ├── csm_helpers.py │ ├── deposits_helpers.py │ ├── easy_track_helpers.py │ ├── event_validators │ │ ├── allowed_recipients_registry.py │ │ ├── anchor.py │ │ ├── aragon.py │ │ ├── burner.py │ │ ├── common.py │ │ ├── composite_post_rebase_beacon_receiver.py │ │ ├── csm.py │ │ ├── dual_governance.py │ │ ├── easy_track.py │ │ ├── erc20_token.py │ │ ├── hash_consensus.py │ │ ├── lido.py │ │ ├── node_operators_registry.py │ │ ├── oracle.py │ │ ├── oracle_report_sanity_checker.py │ │ ├── payout.py │ │ ├── permission.py │ │ ├── proxy.py │ │ ├── relay_allowed_list.py │ │ ├── repo_upgrade.py │ │ ├── rewards_manager.py │ │ ├── staking_router.py │ │ ├── time_constraints.py │ │ ├── token_manager.py │ │ ├── tokens_recoverer.py │ │ ├── unpause.py │ │ ├── vault.py │ │ ├── vesting_escrow.py │ │ └── voting.py │ ├── exit_bus_data.py │ ├── extra_data.py │ ├── helpers.py │ ├── keys_helpers.py │ ├── legacy_oracle_report_helpers.py │ ├── merkle_tree.py │ ├── node_operators_helpers.py │ ├── oracle_report_helpers.py │ ├── reward_wrapper_helpers.py │ ├── simple_dvt_helpers.py │ ├── snapshot_helpers.py │ ├── split_helpers.py │ ├── staking_router_helpers.py │ ├── tx_cost_helper.py │ └── tx_tracing_helpers.py ├── tx_tracing.py ├── txs │ ├── deploy.py │ └── tx-deploy-voting_for_upgrade.json ├── voting.py └── withdrawal_credentials.py └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | charset = utf-8 12 | 13 | # 4 space indentation 14 | [*.py] 15 | indent_style = space 16 | indent_size = 4 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @lidofinance/lido-dao-ops-team 2 | -------------------------------------------------------------------------------- /.github/actions/brownie_fork_tests/action.yml: -------------------------------------------------------------------------------- 1 | name: "Tests" 2 | description: "Run brownie fork tests" 3 | 4 | inputs: 5 | vote: 6 | description: "vote type: normal or large" 7 | required: false 8 | default: "normal" 9 | infura: 10 | description: "infura JSON-RPC provider" 11 | required: true 12 | default: "" 13 | etherscan: 14 | description: "etherscan api key" 15 | required: true 16 | default: "" 17 | 18 | runs: 19 | using: "composite" 20 | steps: 21 | - name: Install poetry 22 | shell: bash 23 | run: | 24 | curl -sSL https://install.python-poetry.org | python3 - 25 | echo "$HOME/.local/bin" >> $GITHUB_PATH 26 | env: 27 | POETRY_VERSION: "1.8.2" 28 | 29 | - name: Set up Python 3.10 30 | uses: actions/setup-python@v4 31 | with: 32 | python-version: "3.10" 33 | cache: poetry 34 | 35 | - name: Setup node.js version 36 | uses: actions/setup-node@v3 37 | with: 38 | node-version: 18.x 39 | cache: "yarn" 40 | 41 | - name: Install poetry requirements 42 | shell: bash 43 | run: poetry install 44 | 45 | - name: Install ganache 46 | shell: bash 47 | run: yarn install --frozen-lockfile 48 | 49 | - name: Import network config to connect brownie with local Ganache 50 | shell: bash 51 | run: poetry run brownie networks import network-config.yaml True 52 | 53 | - name: Show system info 54 | shell: bash 55 | run: | 56 | echo "Memory and swap:" 57 | free -h 58 | echo 59 | swapon --show 60 | echo 61 | df -h 62 | echo 63 | echo "CPU units" 64 | nproc --all 65 | echo "Vote type" 66 | echo ${{ inputs.vote }} 67 | 68 | - name: Run tests 69 | shell: bash 70 | run: > 71 | poetry run 72 | brownie test -ra --network mainnet-fork --durations=20 73 | env: 74 | WEB3_INFURA_PROJECT_ID: ${{ inputs.infura }} 75 | ETHERSCAN_TOKEN: ${{ inputs.etherscan }} 76 | -------------------------------------------------------------------------------- /.github/workflows/large_vote_ci.yml: -------------------------------------------------------------------------------- 1 | name: Large vote CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | - "feat/tests" 8 | - "feat/rc3" 9 | - "feat/rc2" 10 | - "feat/rc1" 11 | - "feat/next-vote" 12 | schedule: 13 | - cron: "0 0 * * TUE" 14 | jobs: 15 | run-tests-large: 16 | name: Brownie fork LARGE tests 17 | runs-on: [protocol-heavy-runners] 18 | timeout-minutes: 150 19 | 20 | services: 21 | hardhat-node: 22 | image: ghcr.io/lidofinance/hardhat-node:2.22.19.2-shanghai 23 | ports: 24 | - 8545:8545 25 | env: 26 | ETH_RPC_URL: "https://mainnet.infura.io/v3/${{ secrets.WEB3_INFURA_PROJECT_ID }}" 27 | 28 | steps: 29 | - name: Enabling swap 30 | run: | 31 | sudo df -h 32 | sudo swapon --show 33 | sudo swapoff -a 34 | sudo touch /swapfile 35 | sudo fallocate -l 32G /swapfile 36 | sudo chmod 600 /swapfile 37 | sudo mkswap /swapfile 38 | sudo swapon /swapfile 39 | sudo swapon --show 40 | - uses: actions/checkout@v3 41 | - name: Main action 42 | uses: ./.github/actions/brownie_fork_tests 43 | with: 44 | vote: "large" 45 | infura: ${{ secrets.WEB3_INFURA_PROJECT_ID }} 46 | etherscan: ${{ secrets.ETHERSCAN_TOKEN }} 47 | -------------------------------------------------------------------------------- /.github/workflows/normal_vote_ci.yml: -------------------------------------------------------------------------------- 1 | name: Normal vote CI 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - "master" 7 | - "feat/tests" 8 | - "feat/rc3" 9 | - "feat/rc2" 10 | - "feat/rc1" 11 | - "feat/next-vote" 12 | jobs: 13 | run-tests-normal: 14 | name: Brownie fork NORMAL tests 15 | runs-on: "ubuntu-latest" 16 | timeout-minutes: 100 17 | 18 | services: 19 | hardhat-node: 20 | image: ghcr.io/lidofinance/hardhat-node:2.22.19.2-shanghai 21 | ports: 22 | - 8545:8545 23 | env: 24 | ETH_RPC_URL: "https://mainnet.infura.io/v3/${{ secrets.WEB3_INFURA_PROJECT_ID }}" 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Main action 29 | uses: ./.github/actions/brownie_fork_tests 30 | with: 31 | vote: "normal" 32 | infura: ${{ secrets.WEB3_INFURA_PROJECT_ID }} 33 | etherscan: ${{ secrets.ETHERSCAN_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .history 3 | .hypothesis/ 4 | .python-version 5 | .pytest_cache 6 | build/ 7 | reports/ 8 | dist/ 9 | .env 10 | _ganache 11 | 12 | # PyCharm 13 | .idea 14 | /.vs/VSWorkspaceState.json 15 | /.vs/slnx.sqlite 16 | /.vs/PythonSettings.json 17 | /.vs/scripts/v17/.suo 18 | 19 | # virtual environment 20 | .direnv 21 | .venv 22 | 23 | /node_modules 24 | yarn-error.log 25 | 26 | # VSCode 27 | .vscode 28 | 29 | # Mac os 30 | .DS_Store 31 | 32 | # local env 33 | .envrc 34 | 35 | .wake 36 | 37 | cache 38 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 22.3.0 4 | hooks: 5 | - id: black 6 | # It is recommended to specify the latest version of Python 7 | # supported by your project here, or alternatively use 8 | # pre-commit's default_language_version, see 9 | # https://pre-commit.com/#top_level-default_language_version 10 | language_version: python3 11 | -------------------------------------------------------------------------------- /archive/scripts/deploy_rewards_manager_tokens_recoverer.py: -------------------------------------------------------------------------------- 1 | from brownie import chain, network, RewardsManagerTokensRecoverer 2 | from utils import config 3 | 4 | 5 | def main(): 6 | deployer = config.get_deployer_account() 7 | 8 | print(f"Current network: {network.show_active()} (chain id: {chain.id})") 9 | print(f"Deployer: {deployer}") 10 | print(f"Agent address: {config.lido_dao_agent_address}") 11 | 12 | print("Proceed? [y/n]: ") 13 | 14 | if not config.prompt_bool(): 15 | print("Aborting") 16 | return 17 | 18 | tx_params = {"from": deployer, "priority_fee": "2 gwei", "max_fee": "300 gwei"} 19 | 20 | RewardsManagerTokensRecoverer.deploy(config.lido_dao_agent_address, tx_params) 21 | -------------------------------------------------------------------------------- /archive/scripts/fallback_2024_10_08_enable_deposits.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fallback voting xx/xx/2024. 3 | 4 | 1. Enable deposits on Optimism L1 Token Bridge 5 | 6 | """ 7 | import time 8 | from brownie import interface 9 | from typing import Dict 10 | from brownie.network.transaction import TransactionReceipt 11 | from utils.voting import bake_vote_items, confirm_vote_script, create_vote 12 | from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description 13 | from utils.agent import agent_forward 14 | from utils.config import ( 15 | get_deployer_account, 16 | get_is_live, 17 | get_priority_fee, 18 | L1_OPTIMISM_TOKENS_BRIDGE, 19 | ) 20 | 21 | description = """ 22 | Fallback voting xx/xx/2024. 23 | 24 | Enable deposits on Optimism L1 Token Bridge 25 | 26 | """ 27 | 28 | 29 | def start_vote(tx_params: Dict[str, str], silent: bool) -> bool | list[int | TransactionReceipt | None]: 30 | """Prepare and run voting.""" 31 | 32 | l1_token_bridge = interface.L1LidoTokensBridge(L1_OPTIMISM_TOKENS_BRIDGE) 33 | 34 | call_script_items = [ 35 | # 1. Enable deposits on Optimism L1 Token Bridge 36 | agent_forward([(l1_token_bridge.address, l1_token_bridge.enableDeposits.encode_input())]), 37 | ] 38 | 39 | vote_desc_items = [ 40 | "1) Enable deposits on Optimism L1 Token Bridge", 41 | ] 42 | 43 | vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) 44 | 45 | if silent: 46 | desc_ipfs = calculate_vote_ipfs_description(description) 47 | else: 48 | desc_ipfs = upload_vote_ipfs_description(description) 49 | 50 | return confirm_vote_script(vote_items, silent, desc_ipfs) and list( 51 | create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) 52 | ) 53 | 54 | 55 | def main(): 56 | tx_params = {"from": get_deployer_account()} 57 | if get_is_live(): 58 | tx_params["priority_fee"] = get_priority_fee() 59 | 60 | vote_id, _ = start_vote(tx_params=tx_params, silent=False) 61 | 62 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 63 | 64 | time.sleep(5) # hack for waiting thread #2. 65 | -------------------------------------------------------------------------------- /archive/scripts/set_lido_locator_implementation.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | 4 | from brownie import interface, accounts 5 | from utils.shapella_upgrade import get_tx_params 6 | from utils.config import ( 7 | prompt_bool, 8 | get_deployer_account, 9 | lido_dao_lido_locator_implementation, 10 | contracts, 11 | network_name 12 | ) 13 | 14 | 15 | def main(): 16 | 17 | deployer = get_deployer_account() if "DEPLOYER" in os.environ else accounts.at("0x2A78076BF797dAC2D25c9568F79b61aFE565B88C", force=True) 18 | 19 | assert interface.OssifiableProxy(contracts.lido_locator).proxy__getAdmin() == deployer 20 | 21 | print(f"==========================") 22 | print(f"=== Network is {network_name()} ===") 23 | print(f"==========================") 24 | print(f"=== Upgrade lido locator implementation to {lido_dao_lido_locator_implementation} ===") 25 | print(f"=== Deployer: {deployer} ===") 26 | 27 | print("Does it look good? [yes/no]") 28 | resume = prompt_bool() 29 | while resume is None: 30 | resume = prompt_bool() 31 | 32 | if not resume: 33 | print("Exit without running.") 34 | return False 35 | 36 | interface.OssifiableProxy(contracts.lido_locator).proxy__upgradeTo( 37 | lido_dao_lido_locator_implementation, get_tx_params(deployer) 38 | ) 39 | 40 | assert interface.OssifiableProxy(contracts.lido_locator).proxy__getImplementation() == lido_dao_lido_locator_implementation 41 | 42 | time.sleep(5) # hack for waiting thread #2. 43 | -------------------------------------------------------------------------------- /archive/scripts/shapella_deploy_template.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from brownie import ShapellaUpgradeTemplate 4 | from utils.config import ( 5 | get_deployer_account, 6 | DEPLOYER_EOA, 7 | ) 8 | 9 | # noinspection PyUnresolvedReferences 10 | from utils.brownie_prelude import * 11 | 12 | 13 | def main(): 14 | deployer = get_deployer_account() 15 | assert deployer == DEPLOYER_EOA, "Need to set DEPLOYER to the DEPLOYER_EOA" 16 | 17 | template = ShapellaUpgradeTemplate.deploy({"from": deployer}) 18 | print(f"Shapella upgrade template is deployed at {template}") 19 | 20 | ShapellaUpgradeTemplate.publish_source(template) 21 | 22 | time.sleep(5) # hack for waiting thread #2. 23 | -------------------------------------------------------------------------------- /archive/scripts/transfer_locator_ownership.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | 4 | from brownie import interface, accounts 5 | from utils.shapella_upgrade import get_tx_params 6 | from utils.config import ( 7 | prompt_bool, 8 | get_deployer_account, 9 | lido_dao_template_address, 10 | lido_dao_lido_locator_implementation, 11 | contracts, 12 | network_name 13 | ) 14 | 15 | 16 | def main(): 17 | 18 | deployer = get_deployer_account() if "DEPLOYER" in os.environ else accounts.at("0x2A78076BF797dAC2D25c9568F79b61aFE565B88C", force=True) 19 | 20 | assert interface.OssifiableProxy(contracts.lido_locator).proxy__getAdmin() == deployer 21 | 22 | print(f"=== Network is {network_name()}") 23 | print(f"=== Lido locator implementation is {interface.OssifiableProxy(contracts.lido_locator).proxy__getImplementation()} ===") 24 | print(f"=== Change lido locator proxy admin to {lido_dao_template_address} ===") 25 | print(f"=== Deployer: {deployer} ===") 26 | 27 | print("Does it look good? [yes/no]") 28 | resume = prompt_bool() 29 | while resume is None: 30 | resume = prompt_bool() 31 | 32 | if not resume: 33 | print("Exit without running.") 34 | return False 35 | 36 | assert interface.OssifiableProxy(contracts.lido_locator).proxy__getImplementation() == lido_dao_lido_locator_implementation 37 | 38 | interface.OssifiableProxy(contracts.lido_locator).proxy__changeAdmin( 39 | lido_dao_template_address, get_tx_params(deployer) 40 | ) 41 | 42 | time.sleep(5) # hack for waiting thread #2. 43 | -------------------------------------------------------------------------------- /archive/scripts/vote_2021_05_omnibus_rewards_limits.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from utils.voting import create_vote 4 | from utils.evm_script import encode_call_script 5 | from utils.node_operators import encode_set_node_operator_staking_limit 6 | from utils.finance import encode_token_transfer 7 | 8 | from utils.config import ( 9 | ldo_token_address, 10 | lido_dao_finance_address, 11 | lido_dao_node_operators_registry, 12 | curve_rewards_manager_address, 13 | get_deployer_account 14 | ) 15 | 16 | try: 17 | from brownie import interface 18 | except ImportError: 19 | print("You're probably running inside Brownie console. Please call:") 20 | print("set_console_globals(interface=interface)") 21 | 22 | 23 | def set_console_globals(**kwargs): 24 | global interface 25 | interface = kwargs['interface'] 26 | 27 | 28 | def start_omnibus_vote(tx_params): 29 | finance = interface.Finance(lido_dao_finance_address) 30 | registry = interface.NodeOperatorsRegistry(lido_dao_node_operators_registry) 31 | evm_script = encode_call_script([ 32 | encode_set_node_operator_staking_limit(id=5, limit=1000, registry=registry), 33 | encode_token_transfer( 34 | token_address=ldo_token_address, 35 | recipient=curve_rewards_manager_address, 36 | amount=(3_750_000 * 10**18), 37 | reference=f'Curve pool LP rewards: transfer to rewards distributor contract', 38 | finance=finance 39 | ) 40 | ]) 41 | return create_vote( 42 | vote_desc=( 43 | f'Omnibus vote: 1) set staking limit for Blockscape to 1000, ' 44 | f'2) reseed Curve LP rewards manager contract with 3,750,000 LDO' 45 | ), 46 | evm_script=evm_script, 47 | tx_params=tx_params 48 | ) 49 | 50 | 51 | def main(): 52 | (vote_id, _) = start_omnibus_vote({'from': get_deployer_account()}) 53 | print(f'Vote created: {vote_id}') 54 | time.sleep(5) # hack: waiting thread 2 55 | -------------------------------------------------------------------------------- /archive/scripts/vote_2022_02_03.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 03/02/2022. 3 | 4 | 1. Referral program payout of 180,278 LDO to finance multisig 0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb 5 | 6 | The vote REJECTED. 7 | 8 | """ 9 | 10 | import time 11 | 12 | from typing import (Dict, Tuple, Optional) 13 | 14 | from brownie.network.transaction import TransactionReceipt 15 | 16 | from utils.finance import make_ldo_payout 17 | from utils.voting import confirm_vote_script, create_vote 18 | from utils.evm_script import encode_call_script 19 | from utils.config import get_deployer_account 20 | 21 | 22 | def start_vote( 23 | tx_params: Dict[str, str], 24 | silent: bool = False 25 | ) -> Tuple[int, Optional[TransactionReceipt]]: 26 | """Prepare and run voting.""" 27 | 28 | encoded_call_script = encode_call_script([ 29 | # 1. Referral program payout of 180,278 LDO to financial multisig 0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb 30 | make_ldo_payout( 31 | target_address='0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb', 32 | ldo_in_wei=180_278 * (10 ** 18), 33 | reference="14th period referral rewards" 34 | ) 35 | ]) 36 | 37 | return confirm_vote_script(encoded_call_script, silent) and create_vote( 38 | vote_desc=( 39 | 'Omnibus vote: ' 40 | '1) Allocate 180,278 LDO tokens for the 14th period referral rewards.' 41 | ), 42 | evm_script=encoded_call_script, 43 | tx_params=tx_params 44 | ) 45 | 46 | 47 | def main(): 48 | vote_id, _ = start_vote({ 49 | 'from': get_deployer_account(), 50 | 'max_fee': '100 gwei', 51 | 'priority_fee': '2 gwei' 52 | }) 53 | 54 | vote_id >= 0 and print(f'Vote created: {vote_id}.') 55 | 56 | time.sleep(5) # hack for waiting thread #2. 57 | -------------------------------------------------------------------------------- /archive/scripts/vote_2022_02_04.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 04/02/2022. 3 | 4 | 1. Referral program payout of 180,278 LDO to finance multisig 0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb 5 | 6 | The vote REJECTED. 7 | 8 | """ 9 | 10 | import time 11 | 12 | from typing import (Dict, Tuple, Optional) 13 | 14 | from brownie.network.transaction import TransactionReceipt 15 | 16 | from utils.finance import make_ldo_payout 17 | from utils.voting import confirm_vote_script, create_vote 18 | from utils.evm_script import encode_call_script 19 | from utils.config import get_deployer_account 20 | 21 | 22 | def start_vote( 23 | tx_params: Dict[str, str], 24 | silent: bool = False 25 | ) -> Tuple[int, Optional[TransactionReceipt]]: 26 | """Prepare and run voting.""" 27 | 28 | encoded_call_script = encode_call_script([ 29 | # 1. Referral program payout of 180,278 LDO to financial multisig 0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb 30 | make_ldo_payout( 31 | target_address='0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb', 32 | ldo_in_wei=180_278 * (10 ** 18), 33 | reference="14th period referral rewards" 34 | ) 35 | ]) 36 | 37 | return confirm_vote_script(encoded_call_script, silent) and create_vote( 38 | vote_desc=( 39 | 'Omnibus vote: ' 40 | '1) Allocate 180,278 LDO tokens for the 14th period referral rewards.' 41 | ), 42 | evm_script=encoded_call_script, 43 | tx_params=tx_params 44 | ) 45 | 46 | 47 | def main(): 48 | vote_id, _ = start_vote({ 49 | 'from': get_deployer_account(), 50 | 'max_fee': '100 gwei', 51 | 'priority_fee': '2 gwei' 52 | }) 53 | 54 | vote_id >= 0 and print(f'Vote created: {vote_id}.') 55 | 56 | time.sleep(5) # hack for waiting thread #2. 57 | -------------------------------------------------------------------------------- /archive/scripts/vote_2022_02_07.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 07/02/2022. 3 | 4 | 1. Referral program payout of 180,278 LDO to finance multisig 0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb 5 | 6 | Vote passed & executed on Feb-08-2022 12:34:18 PM +UTC, block #14165452. 7 | TX URL: https://etherscan.io/tx/0x4d195c86cc08136b832f1f2f0c75413647f32f7da1dea6dbae71d608d14ab480 8 | 9 | """ 10 | 11 | import time 12 | 13 | from typing import (Dict, Tuple, Optional) 14 | 15 | from brownie.network.transaction import TransactionReceipt 16 | 17 | from utils.finance import make_ldo_payout 18 | from utils.voting import confirm_vote_script, create_vote 19 | from utils.evm_script import encode_call_script 20 | from utils.config import get_deployer_account 21 | 22 | 23 | def start_vote( 24 | tx_params: Dict[str, str], 25 | silent: bool = False 26 | ) -> Tuple[int, Optional[TransactionReceipt]]: 27 | """Prepare and run voting.""" 28 | 29 | encoded_call_script = encode_call_script([ 30 | # 1. Referral program payout of 180,278 LDO to financial multisig 0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb 31 | make_ldo_payout( 32 | target_address='0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb', 33 | ldo_in_wei=180_278 * (10 ** 18), 34 | reference="14th period referral rewards" 35 | ) 36 | ]) 37 | 38 | return confirm_vote_script(encoded_call_script, silent) and create_vote( 39 | vote_desc=( 40 | 'Omnibus vote: ' 41 | '1) Allocate 180,278 LDO tokens for the 14th period referral rewards.' 42 | ), 43 | evm_script=encoded_call_script, 44 | tx_params=tx_params 45 | ) 46 | 47 | 48 | def main(): 49 | vote_id, _ = start_vote({ 50 | 'from': get_deployer_account(), 51 | 'max_fee': '300 gwei', 52 | 'priority_fee': '2 gwei' 53 | }) 54 | 55 | vote_id >= 0 and print(f'Vote created: {vote_id}.') 56 | 57 | time.sleep(5) # hack for waiting thread #2. 58 | -------------------------------------------------------------------------------- /archive/scripts/vote_2022_03_03.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 03/03/2022. 3 | 4 | 1. Referral program payout of 412,082 LDO to finance multisig 0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb 5 | 6 | Vote passed & executed on Mar-04-2022 12:20:58 PM +UTC, block #14320412. 7 | TX URL: https://etherscan.io/tx/0x26260b616ece73da2b76129130d0b63b622efee2623c38e17ab31bd1963afa4e 8 | 9 | """ 10 | 11 | import time 12 | 13 | from typing import (Dict, Tuple, Optional) 14 | 15 | from brownie.network.transaction import TransactionReceipt 16 | 17 | from utils.finance import make_ldo_payout 18 | from utils.voting import confirm_vote_script, create_vote 19 | from utils.evm_script import encode_call_script 20 | from utils.config import get_deployer_account 21 | 22 | 23 | def start_vote( 24 | tx_params: Dict[str, str], 25 | silent: bool = False 26 | ) -> Tuple[int, Optional[TransactionReceipt]]: 27 | """Prepare and run voting.""" 28 | 29 | encoded_call_script = encode_call_script([ 30 | # 1. Referral program payout of 412,082 LDO to financial multisig 0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb 31 | make_ldo_payout( 32 | target_address='0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb', 33 | ldo_in_wei=412_082 * (10 ** 18), 34 | reference="16th period referral rewards" 35 | ), 36 | ]) 37 | 38 | return confirm_vote_script(encoded_call_script, silent) and create_vote( 39 | vote_desc=( 40 | 'Omnibus vote: ' 41 | '1) Allocate 412,082 LDO tokens for the 16th period referral rewards.' 42 | ), 43 | evm_script=encoded_call_script, 44 | tx_params=tx_params 45 | ) 46 | 47 | 48 | def main(): 49 | vote_id, _ = start_vote({ 50 | 'from': get_deployer_account(), 51 | 'max_fee': '300 gwei', 52 | 'priority_fee': '2 gwei' 53 | }) 54 | 55 | vote_id >= 0 and print(f'Vote created: {vote_id}.') 56 | 57 | time.sleep(5) # hack for waiting thread #2. 58 | -------------------------------------------------------------------------------- /archive/scripts/vote_2022_06_21_goerli_2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 20/06/2022 for Goerli. 3 | 4 | 1. Push new voting app version to Voting Repo. 5 | 6 | """ 7 | 8 | import time 9 | 10 | from typing import Dict, Tuple, Optional 11 | 12 | from brownie.network.transaction import TransactionReceipt 13 | 14 | from utils.voting import confirm_vote_script, create_vote, bake_vote_items 15 | from utils.repo import add_implementation_to_voting_app_repo 16 | from utils.config import ( 17 | get_deployer_account, 18 | get_is_live, 19 | ) 20 | 21 | update_voting_app = { 22 | "new_address": "0x12D103a07Ac0429519C77E96781dFD5186119582", 23 | "content_uri": "0x697066733a516d657369564c547931646476476f4c6e6f504367466551577446396974774e755956756661766e595761363567", 24 | "version": (4, 1, 0), 25 | } 26 | 27 | 28 | def start_vote(tx_params: Dict[str, str], silent: bool = False) -> Tuple[int, Optional[TransactionReceipt]]: 29 | """Prepare and run voting.""" 30 | 31 | vote_items = bake_vote_items( 32 | vote_desc_items=[ 33 | "1) Push new Voting app version to Voting Repo", 34 | ], 35 | call_script_items=[ 36 | # 1. Push new voting app version to Voting Repo 0x41D65FA420bBC714686E798a0eB0Df3799cEF092 37 | add_implementation_to_voting_app_repo( 38 | update_voting_app["version"], 39 | update_voting_app["new_address"], 40 | update_voting_app["content_uri"], 41 | ), 42 | ], 43 | ) 44 | 45 | return confirm_vote_script(vote_items, silent) and create_vote(vote_items, tx_params=tx_params) 46 | 47 | 48 | def main(): 49 | tx_params = {"from": get_deployer_account()} 50 | 51 | if get_is_live(): 52 | tx_params["max_fee"] = "300 gwei" 53 | tx_params["priority_fee"] = "2 gwei" 54 | 55 | vote_id, _ = start_vote(tx_params=tx_params) 56 | 57 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 58 | 59 | time.sleep(5) # hack for waiting thread #2. 60 | -------------------------------------------------------------------------------- /archive/scripts/vote_2022_06_21_goerli_3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 21/06/2022 for Goerli. 3 | 4 | 1. Push new voting app version to Voting Repo. 5 | 6 | """ 7 | 8 | import time 9 | 10 | from typing import Dict, Tuple, Optional 11 | 12 | from brownie.network.transaction import TransactionReceipt 13 | 14 | from utils.voting import confirm_vote_script, create_vote, bake_vote_items 15 | from utils.repo import add_implementation_to_voting_app_repo 16 | from utils.config import ( 17 | get_deployer_account, 18 | get_is_live, 19 | ) 20 | 21 | update_voting_app = { 22 | "new_address": "0x12D103a07Ac0429519C77E96781dFD5186119582", 23 | "content_uri": "0x697066733a516d506f7478377a484743674265394445684d6f4238336572564a75764d74335971436e6454657a575652706441", 24 | "version": (4, 1, 1), 25 | } 26 | 27 | 28 | def start_vote(tx_params: Dict[str, str], silent: bool = False) -> Tuple[int, Optional[TransactionReceipt]]: 29 | """Prepare and run voting.""" 30 | 31 | vote_items = bake_vote_items( 32 | vote_desc_items=[ 33 | "1) Push new Voting app version to Voting Repo", 34 | ], 35 | call_script_items=[ 36 | # 1. Push new voting app version to Voting Repo 0x41D65FA420bBC714686E798a0eB0Df3799cEF092 37 | add_implementation_to_voting_app_repo( 38 | update_voting_app["version"], 39 | update_voting_app["new_address"], 40 | update_voting_app["content_uri"], 41 | ), 42 | ], 43 | ) 44 | 45 | return confirm_vote_script(vote_items, silent) and create_vote(vote_items, tx_params=tx_params) 46 | 47 | 48 | def main(): 49 | tx_params = {"from": get_deployer_account()} 50 | 51 | if get_is_live(): 52 | tx_params["max_fee"] = "300 gwei" 53 | tx_params["priority_fee"] = "2 gwei" 54 | 55 | vote_id, _ = start_vote(tx_params=tx_params) 56 | 57 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 58 | 59 | time.sleep(5) # hack for waiting thread #2. 60 | -------------------------------------------------------------------------------- /archive/scripts/vote_2022_07_12.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 12/07/2022. 3 | 4 | 1. Redirect DAO Insurance Fund fees to DAO Treasury 5 | 6 | Vote passed & executed on Jul-15-2022 02:02:21 PM +UTC, block 15147684. 7 | 8 | """ 9 | # noinspection PyUnresolvedReferences 10 | 11 | import time 12 | 13 | from typing import Dict, Tuple, Optional 14 | 15 | from brownie.network.transaction import TransactionReceipt 16 | 17 | from utils.voting import bake_vote_items, confirm_vote_script, create_vote 18 | from utils.config import get_deployer_account, get_is_live, contracts 19 | from utils.brownie_prelude import * 20 | 21 | 22 | def encode_swap_treasury_and_insurance_fees() -> Tuple[str, str]: 23 | lido: interface.Lido = contracts.lido 24 | 25 | treasury_bp = 5000 26 | insurance_bp = 0 27 | operators_bp = 5000 28 | 29 | return lido.address, lido.setFeeDistribution.encode_input( 30 | treasury_bp, 31 | insurance_bp, 32 | operators_bp 33 | ) 34 | 35 | 36 | def start_vote( 37 | tx_params: Dict[str, str], 38 | silent: bool = False, 39 | ) -> Tuple[int, Optional[TransactionReceipt]]: 40 | """Prepare and run voting.""" 41 | 42 | call_script_items = [ 43 | # 1. Redirect DAO Insurance Fund fees to DAO Treasury 44 | encode_swap_treasury_and_insurance_fees(), 45 | ] 46 | 47 | # NB: In case of single vote item the ending period is added automatically 48 | vote_desc_items = [ 49 | "1) Redirect DAO Insurance Fund fees to DAO Treasury", 50 | ] 51 | 52 | vote_items = bake_vote_items(vote_desc_items, call_script_items) 53 | return confirm_vote_script(vote_items, silent) and create_vote(vote_items, tx_params) 54 | 55 | 56 | def main(): 57 | tx_params = {"from": get_deployer_account()} 58 | 59 | if get_is_live(): 60 | tx_params["max_fee"] = "300 gwei" 61 | tx_params["priority_fee"] = "2 gwei" 62 | 63 | vote_id, _ = start_vote(tx_params=tx_params) 64 | 65 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 66 | 67 | time.sleep(5) # hack for waiting thread #2. 68 | -------------------------------------------------------------------------------- /archive/scripts/vote_2022_11_10.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 10/11/2022. 3 | 4 | 1. Increase max APR security check limit to 1750 bp 5 | 6 | Vote passed & executed on Nov-13-2022 10:41:23 AM +UTC, block 15960570 7 | """ 8 | import time 9 | from typing import Dict, Optional, Tuple 10 | 11 | from brownie.network.transaction import TransactionReceipt 12 | 13 | from utils.voting import bake_vote_items, confirm_vote_script, create_vote 14 | from utils.brownie_prelude import * 15 | from utils.config import ( 16 | get_deployer_account, 17 | get_is_live, 18 | contracts, 19 | ) 20 | 21 | ALLOWED_BEACON_BALANCE_INCREASE_LIMIT = 1750 22 | 23 | 24 | def encode_set_allow_beacon_balance_increase_limit(): 25 | oracle: interface.LidoOracle = contracts.lido_oracle 26 | 27 | return oracle.address, oracle.setAllowedBeaconBalanceAnnualRelativeIncrease.encode_input( 28 | ALLOWED_BEACON_BALANCE_INCREASE_LIMIT 29 | ) 30 | 31 | 32 | def start_vote( 33 | tx_params: Dict[str, str], 34 | silent: bool = False, 35 | ) -> Tuple[int, Optional[TransactionReceipt]]: 36 | """Prepare and run voting.""" 37 | 38 | call_script_items = [ 39 | # 1. Increase max APR security check limit to 1750 bp 40 | encode_set_allow_beacon_balance_increase_limit(), 41 | ] 42 | 43 | # NB: In case of single vote item the ending period is added automatically 44 | vote_desc_items = [ 45 | "1) Increase max APR security check limit to 1750 bp", 46 | ] 47 | 48 | vote_items = bake_vote_items(vote_desc_items, call_script_items) 49 | return confirm_vote_script(vote_items, silent) and create_vote(vote_items, tx_params) 50 | 51 | 52 | def main(): 53 | tx_params = {"from": get_deployer_account()} 54 | 55 | if get_is_live(): 56 | tx_params["max_fee"] = "200 gwei" 57 | tx_params["priority_fee"] = "2 gwei" 58 | 59 | vote_id, _ = start_vote(tx_params=tx_params) 60 | 61 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 62 | 63 | time.sleep(5) # hack for waiting thread #2. 64 | -------------------------------------------------------------------------------- /archive/scripts/vote_2023_02_21_goerli.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 21/02/2023. 3 | 1. Add TRP top up EVM script factory 0x43f33C52156d1Fb2eA24d82aBfD342E69835E79f 4 | 5 | !! Goerli only 6 | """ 7 | 8 | import time 9 | 10 | from typing import Dict, Tuple, Optional, List 11 | 12 | from brownie import interface 13 | from brownie.network.transaction import TransactionReceipt 14 | from utils.voting import bake_vote_items, confirm_vote_script, create_vote 15 | from utils.easy_track import add_evmscript_factory, create_permissions 16 | from utils.permission_parameters import Param, SpecialArgumentID, ArgumentValue, Op 17 | 18 | from utils.config import ( 19 | get_deployer_account, 20 | lido_dao_finance_address, 21 | ) 22 | 23 | 24 | def start_vote(tx_params: Dict[str, str], silent: bool = False) -> Tuple[int, Optional[TransactionReceipt]]: 25 | """Prepare and run voting.""" 26 | 27 | finance = interface.Finance(lido_dao_finance_address) 28 | 29 | TRP_topup_factory = interface.TopUpAllowedRecipients("0x43f33C52156d1Fb2eA24d82aBfD342E69835E79f") 30 | TRP_registry = interface.AllowedRecipientRegistry("0x8C96a6522aEc036C4a384f8B7e05D93d6f3Dae39") 31 | 32 | call_script_items = [ 33 | # 1. Add TRP top up EVM script factory 0x43f33C52156d1Fb2eA24d82aBfD342E69835E79f 34 | add_evmscript_factory( 35 | factory=TRP_topup_factory, 36 | permissions=create_permissions(finance, "newImmediatePayment") 37 | + create_permissions(TRP_registry, "updateSpentAmount")[2:], 38 | ), 39 | ] 40 | 41 | vote_desc_items = [ 42 | "1) Add TRP top up EVM script factory 0x43f33C52156d1Fb2eA24d82aBfD342E69835E79f", 43 | ] 44 | 45 | vote_items = bake_vote_items(vote_desc_items, call_script_items) 46 | 47 | return confirm_vote_script(vote_items, silent) and create_vote(vote_items, tx_params) 48 | 49 | 50 | def main(): 51 | vote_id, _ = start_vote({"from": get_deployer_account(), "max_fee": "300 gwei", "priority_fee": "2 gwei"}) 52 | 53 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 54 | 55 | time.sleep(5) # hack for waiting thread #2. 56 | -------------------------------------------------------------------------------- /archive/scripts/vote_2023_04_18.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 18/04/2023. 3 | 1. Set Staking limit for Node Operator RockLogic GmbH to 5800 4 | 5 | Vote passed & executed on Apr-21-2023 04:02:11 PM +UTC, block 17095814 6 | """ 7 | 8 | import time 9 | 10 | from typing import Dict, Tuple, Optional, List 11 | 12 | from brownie import interface 13 | from brownie.network.transaction import TransactionReceipt 14 | from utils.voting import bake_vote_items, confirm_vote_script, create_vote 15 | 16 | from utils.config import ( 17 | get_deployer_account, 18 | lido_dao_node_operators_registry, 19 | ) 20 | from utils.node_operators import ( 21 | encode_set_node_operator_staking_limit 22 | ) 23 | 24 | def start_vote(tx_params: Dict[str, str], silent: bool = False) -> Tuple[int, Optional[TransactionReceipt]]: 25 | """Prepare and run voting.""" 26 | 27 | NO_registry = interface.NodeOperatorsRegistry(lido_dao_node_operators_registry) 28 | RockLogicGmbH_id = 22 29 | RockLogicGmbH_limit = 5800 30 | 31 | call_script_items = [ 32 | # 1. Set Staking limit for node operator RockLogic GmbH to 5800 33 | encode_set_node_operator_staking_limit(RockLogicGmbH_id, RockLogicGmbH_limit, NO_registry), 34 | ] 35 | 36 | vote_desc_items = [ 37 | "1) Set Staking limit for Node Operator RockLogic GmbH to 5800", 38 | ] 39 | 40 | vote_items = bake_vote_items(vote_desc_items, call_script_items) 41 | 42 | return confirm_vote_script(vote_items, silent) and create_vote(vote_items, tx_params) 43 | 44 | 45 | def main(): 46 | vote_id, _ = start_vote({"from": get_deployer_account(), "priority_fee": "4 gwei"}) 47 | 48 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 49 | 50 | time.sleep(5) # hack for waiting thread #2. 51 | -------------------------------------------------------------------------------- /archive/scripts/vote_2023_05_03_goerli.py: -------------------------------------------------------------------------------- 1 | """ 2 | Technical vote for Goerli network 3 | 4 | 1. Change TRP manager to 0xde0a8383c0c16c472bdf540e38ad9d85b12eff1e (shared QA wallet) 5 | """ 6 | 7 | import time 8 | 9 | from typing import Dict, Tuple, Optional, List 10 | 11 | from brownie import interface 12 | from brownie.network.transaction import TransactionReceipt 13 | from utils.voting import bake_vote_items, confirm_vote_script, create_vote 14 | from utils.agent import agent_forward 15 | 16 | from utils.config import ( 17 | get_deployer_account, 18 | trp_escrow_factory_address, 19 | ) 20 | 21 | def start_vote(tx_params: Dict[str, str], silent: bool = False) -> Tuple[int, Optional[TransactionReceipt]]: 22 | """Prepare and run voting.""" 23 | 24 | trp_factory = interface.VestingEscrowFactory(trp_escrow_factory_address) 25 | QA_shared_wallet = "0xde0a8383c0c16c472bdf540e38ad9d85b12eff1e" 26 | 27 | call_script_items = [ 28 | # 1. Change TRP manager to 0xde0a8383c0c16c472bdf540e38ad9d85b12eff1e (shared QA wallet) 29 | agent_forward([( 30 | trp_factory.address, 31 | trp_factory.change_manager.encode_input(QA_shared_wallet), 32 | )]) 33 | ] 34 | 35 | vote_desc_items = [ 36 | "1. Change TRP manager to 0xde0a8383c0c16c472bdf540e38ad9d85b12eff1e (shared QA wallet)", 37 | ] 38 | 39 | vote_items = bake_vote_items(vote_desc_items, call_script_items) 40 | 41 | return confirm_vote_script(vote_items, silent) and create_vote(vote_items, tx_params) 42 | 43 | 44 | def main(): 45 | print(get_deployer_account().address) 46 | vote_id, _ = start_vote({"from": get_deployer_account(), "max_fee": "300 gwei", "priority_fee": "2 gwei"}) 47 | 48 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 49 | 50 | time.sleep(5) # hack for waiting thread #2. 51 | -------------------------------------------------------------------------------- /archive/scripts/vote_2023_06_20_2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Voting 20/06/2023 part 2 3 | 4 | 1) Stake all Treasury ETH in Lido 5 | 6 | Vote passed & executed on Jun-30-2023 06:47:35 PM +UTC, block 17593968 7 | """ 8 | 9 | import time 10 | 11 | from typing import Dict, Tuple, Optional 12 | 13 | from brownie.network.transaction import TransactionReceipt 14 | from utils.agent import agent_execute 15 | from brownie import ZERO_ADDRESS 16 | 17 | from utils.voting import bake_vote_items, confirm_vote_script, create_vote 18 | 19 | from utils.config import ( 20 | get_deployer_account, 21 | get_is_live, 22 | contracts, 23 | get_priority_fee, 24 | ) 25 | 26 | 27 | def start_vote(tx_params: Dict[str, str], silent: bool) -> Tuple[int, Optional[TransactionReceipt]]: 28 | """Prepare and run voting.""" 29 | 30 | current_agent_balance = 20304356786192398999068 31 | 32 | submit_calldata = contracts.lido.submit.encode_input(ZERO_ADDRESS) 33 | 34 | call_script_items = [ 35 | agent_execute( 36 | target=contracts.lido.address, 37 | value=current_agent_balance, 38 | data=submit_calldata 39 | ), 40 | ] 41 | 42 | vote_desc_items = [ 43 | "1) Stake all Treasury ETH in Lido" 44 | ] 45 | 46 | vote_items = bake_vote_items(vote_desc_items, call_script_items) 47 | 48 | return confirm_vote_script(vote_items, silent) and list(create_vote(vote_items, tx_params)) 49 | 50 | 51 | def main(): 52 | tx_params = {"from": get_deployer_account()} 53 | 54 | if get_is_live(): 55 | tx_params["priority_fee"] = get_priority_fee() 56 | 57 | vote_id, _ = start_vote(tx_params=tx_params, silent=False) 58 | 59 | vote_id >= 0 and print(f"Vote created: {vote_id}.") 60 | 61 | time.sleep(5) # hack for waiting thread #2. 62 | -------------------------------------------------------------------------------- /archive/tests/_test_fallback_2024_10_08_enable_deposits.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for fallback enable deposits voting xx/xx/2024 3 | """ 4 | from scripts.fallback_enable_deposits import start_vote 5 | from brownie import interface 6 | from utils.test.tx_tracing_helpers import * 7 | from utils.config import ( 8 | contracts, 9 | AGENT, 10 | L1_OPTIMISM_TOKENS_BRIDGE, 11 | ) 12 | 13 | L1_TOKEN_BRIDGE_OLD_IMPL: str = "0x29C5c51A031165CE62F964966A6399b81165EFA4" 14 | 15 | 16 | def test_vote(helpers, accounts, vote_ids_from_env, ldo_holder): 17 | l1_token_bridge_proxy = interface.OssifiableProxy(L1_OPTIMISM_TOKENS_BRIDGE) 18 | l1_token_bridge = interface.L1LidoTokensBridge(L1_OPTIMISM_TOKENS_BRIDGE) 19 | 20 | # Prepare required state for the voting 21 | if l1_token_bridge.isDepositsEnabled(): 22 | agent = accounts.at(AGENT, force=True) 23 | l1_token_bridge.disableDeposits({"from": agent}) 24 | 25 | # Disabled deposits is the starting condition for the vote 26 | assert not l1_token_bridge.isDepositsEnabled() 27 | 28 | # L1 Bridge hasn't been upgraded (just in case check) 29 | assert l1_token_bridge_proxy.proxy__getImplementation() == L1_TOKEN_BRIDGE_OLD_IMPL 30 | 31 | # START VOTE 32 | if len(vote_ids_from_env) > 0: 33 | (vote_id,) = vote_ids_from_env 34 | else: 35 | vote_id, _ = start_vote({"from": ldo_holder}, silent=True) 36 | 37 | vote_tx = helpers.execute_vote(accounts, vote_id, contracts.voting) 38 | 39 | print(f"voteId = {vote_id}, gasUsed = {vote_tx.gas_used}") 40 | 41 | # validate vote events 42 | assert count_vote_items_by_events(vote_tx, contracts.voting) == 1, "Incorrect voting items count" 43 | 44 | # Check the deposits are indeed enabled 45 | assert l1_token_bridge.isDepositsEnabled() 46 | -------------------------------------------------------------------------------- /archive/tests/_test_fallback_2024_10_08_rollback_l1.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting xx/xx/2024 3 | """ 4 | from scripts.upgrade_2024_10_08 import start_vote 5 | from test_2024_10_08 import check_pre_upgrade_state, check_post_upgrade_state 6 | from scripts.fallback_rollback_l1 import start_vote as start_vote_fallback 7 | from brownie import interface 8 | from utils.test.tx_tracing_helpers import * 9 | from utils.config import ( 10 | contracts, 11 | AGENT, 12 | L1_OPTIMISM_TOKENS_BRIDGE 13 | ) 14 | 15 | def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env): 16 | l1_token_bridge = interface.L1LidoTokensBridge(L1_OPTIMISM_TOKENS_BRIDGE) 17 | 18 | # Prepare required state for the voting 19 | if l1_token_bridge.isDepositsEnabled(): 20 | agent = accounts.at(AGENT, force=True) 21 | l1_token_bridge.disableDeposits({"from": agent}) 22 | 23 | check_pre_upgrade_state() 24 | wsteth_bridge_balance_before = contracts.wsteth.balanceOf(L1_OPTIMISM_TOKENS_BRIDGE) 25 | 26 | # START VOTE 27 | if len(vote_ids_from_env) > 0: 28 | (vote_id,) = vote_ids_from_env 29 | else: 30 | vote_id, _ = start_vote({"from": ldo_holder}, silent=True) 31 | 32 | vote_tx = helpers.execute_vote(accounts, vote_id, contracts.voting) 33 | 34 | print(f"voteId = {vote_id}, gasUsed = {vote_tx.gas_used}") 35 | 36 | # validate vote events 37 | assert count_vote_items_by_events(vote_tx, contracts.voting) == 5, "Incorrect voting items count" 38 | 39 | evs = group_voting_events(vote_tx) 40 | check_post_upgrade_state(evs) 41 | assert wsteth_bridge_balance_before == contracts.wsteth.balanceOf(L1_OPTIMISM_TOKENS_BRIDGE) 42 | 43 | # START FALLBACK VOTE 44 | if len(vote_ids_from_env) > 0: 45 | (vote_id,) = vote_ids_from_env 46 | else: 47 | vote_id, _ = start_vote_fallback({"from": ldo_holder}, silent=True) 48 | 49 | vote_tx = helpers.execute_vote(accounts, vote_id, contracts.voting) 50 | 51 | # validate vote events 52 | assert count_vote_items_by_events(vote_tx, contracts.voting) == 3, "Incorrect voting items count" 53 | 54 | check_pre_upgrade_state() 55 | -------------------------------------------------------------------------------- /archive/tests/permission_parameters.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | from utils.permission_parameters import parse 4 | from scripts.setup_easytrack_limits import amount_limits 5 | 6 | 7 | def test_parse_params(): 8 | params_string = "[92293023275763683840113128248807091592764448837194831896495561762951005208577, " \ 9 | "1766847064778384329583298017963818329295263166690245845603698629684435762, " \ 10 | "915226779555203082724148105384831791198636658581575620306992012269577043968, " \ 11 | "92293023275763683840113128248807091592764448837194831896550901995185018765316, " \ 12 | "1766847064778384329583297500742918515827483896875618958121606201292619776, " \ 13 | "915226779555203082724148105384831791198636658581570621306992012269577043968, " \ 14 | "92293023275763683840113128248807091592764448837194831896606242227419032322055, " \ 15 | "1766847064778384329583298112125205347448951130763417879965542220946677007, " \ 16 | "915226779555203082724148105384831791198636658581570720306992012269577043968, " \ 17 | "92293023275763683840113128248807091592764448837194831896661582459653045878794, " \ 18 | "1766847064778384329583298496844153738501895917213557546662745584162045572, " \ 19 | "915226779555203082724148105384831791198636658581570621306992012269577043968, " \ 20 | "92736501889023058306838535921493564140237147295310612254928744687245734182912] " 21 | params = ast.literal_eval(params_string) 22 | parsed = list(map(lambda x: parse(x), params)) 23 | assert parsed == amount_limits() 24 | 25 | 26 | def test_params_str(): 27 | param_int = 92293023275763683840113128248807091592764448837194831896495561762951005208577 28 | param = parse(param_int) 29 | assert str(param) == 'Param(ArgumentID=LOGIC_OP_PARAM_ID, op=Op.IF_ELSE, if=1 then=2 else=3)' 30 | -------------------------------------------------------------------------------- /archive/tests/shapella_acceptance_goerli/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from utils.config import contracts 4 | from utils.import_current_votes import is_there_any_vote_scripts, start_and_execute_votes 5 | 6 | 7 | @pytest.fixture(scope="module", autouse=is_there_any_vote_scripts()) 8 | def autoexecute_vote(helpers, vote_ids_from_env, accounts): 9 | if vote_ids_from_env: 10 | helpers.execute_votes(accounts, vote_ids_from_env, contracts.voting, topup="0.5 ether") 11 | else: 12 | start_and_execute_votes(contracts.voting, helpers) 13 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/archive/tests/shapella_upgrade_acceptance/__init__.py -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from utils.config import contracts 4 | from utils.import_current_votes import is_there_any_vote_scripts, start_and_execute_votes 5 | 6 | 7 | @pytest.fixture(scope="module", autouse=True) 8 | def shared_setup(module_isolation): 9 | pass 10 | 11 | 12 | @pytest.fixture(scope="module", autouse=is_there_any_vote_scripts()) 13 | def autoexecute_vote(helpers, vote_ids_from_env, accounts): 14 | if vote_ids_from_env: 15 | helpers.execute_votes(accounts, vote_ids_from_env, contracts.voting, topup="0.5 ether") 16 | else: 17 | start_and_execute_votes(contracts.voting, helpers) 18 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/test_burner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface # type: ignore 3 | 4 | from utils.config import ( 5 | contracts, 6 | BURNER, 7 | TOTAL_NON_COVER_SHARES_BURNT, 8 | TOTAL_COVER_SHARES_BURNT, 9 | ) 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def contract() -> interface.Burner: 14 | return interface.Burner(BURNER) 15 | 16 | 17 | def test_links(contract): 18 | assert contract.STETH() == contracts.lido 19 | assert contract.TREASURY() == contracts.agent 20 | 21 | 22 | def test_burner(contract): 23 | shares_requested_to_burn = contract.getSharesRequestedToBurn() 24 | 25 | assert shares_requested_to_burn["coverShares"] == 0 26 | assert shares_requested_to_burn["nonCoverShares"] == 0 27 | 28 | assert contract.getCoverSharesBurnt() == TOTAL_COVER_SHARES_BURNT 29 | assert contract.getExcessStETH() == 0 30 | 31 | assert contract.getNonCoverSharesBurnt() == TOTAL_NON_COVER_SHARES_BURNT 32 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/test_el_rewards_vault.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface # type: ignore 3 | 4 | from utils.config import contracts, EXECUTION_LAYER_REWARDS_VAULT 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def contract() -> interface.WithdrawalVault: 9 | return interface.WithdrawalVault(EXECUTION_LAYER_REWARDS_VAULT) 10 | 11 | 12 | def test_vault(contract): 13 | assert contract.LIDO() == contracts.lido 14 | assert contract.TREASURY() == contracts.agent 15 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/test_gate_seal_acceptance.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract, interface, web3 # type: ignore 3 | from brownie.network.account import Account 4 | 5 | from utils.config import GATE_SEAL_EXPIRY_TIMESTAMP, GATE_SEAL_PAUSE_DURATION, contracts, GATE_SEAL, GATE_SEAL_COMMITTEE 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def gate_seal_committee(accounts) -> Account: 10 | return accounts.at(GATE_SEAL_COMMITTEE, force=True) 11 | 12 | 13 | @pytest.fixture(scope="module") 14 | def contract() -> Contract: 15 | return interface.GateSeal(GATE_SEAL) 16 | 17 | 18 | def test_gate_seal(contract: Contract, gate_seal_committee: Account): 19 | assert contract.get_sealing_committee() == gate_seal_committee 20 | 21 | sealables = contract.get_sealables() 22 | assert len(sealables) == 2 23 | assert contracts.validators_exit_bus_oracle.address in sealables 24 | assert contracts.withdrawal_queue.address in sealables 25 | 26 | _check_role(contracts.validators_exit_bus_oracle, "PAUSE_ROLE", contract.address) 27 | _check_role(contracts.withdrawal_queue, "PAUSE_ROLE", contract.address) 28 | 29 | assert contract.get_seal_duration_seconds() == GATE_SEAL_PAUSE_DURATION 30 | assert contract.get_expiry_timestamp() == GATE_SEAL_EXPIRY_TIMESTAMP 31 | assert not contract.is_expired() 32 | 33 | 34 | def _check_role(contract: Contract, role: str, holder: str): 35 | role_bytes = web3.keccak(text=role).hex() 36 | assert contract.getRoleMemberCount(role_bytes) == 1, f"Role {role} on {contract} should have exactly one holder" 37 | assert contract.getRoleMember(role_bytes, 0) == holder, f"Role {role} holder on {contract} should be {holder}" 38 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/test_oracle_daemon_config.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface # type: ignore 3 | 4 | from utils.config import ( 5 | ORACLE_DAEMON_CONFIG, 6 | NORMALIZED_CL_REWARD_PER_EPOCH, 7 | NORMALIZED_CL_REWARD_MISTAKE_RATE_BP, 8 | REBASE_CHECK_NEAREST_EPOCH_DISTANCE, 9 | REBASE_CHECK_DISTANT_EPOCH_DISTANCE, 10 | VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS, 11 | VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS, 12 | PREDICTION_DURATION_IN_SLOTS, 13 | FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT, 14 | NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP, 15 | ) 16 | 17 | 18 | mainnet_config: dict[str, int] = { 19 | "NORMALIZED_CL_REWARD_PER_EPOCH": NORMALIZED_CL_REWARD_PER_EPOCH, 20 | "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": NORMALIZED_CL_REWARD_MISTAKE_RATE_BP, 21 | "REBASE_CHECK_NEAREST_EPOCH_DISTANCE": REBASE_CHECK_NEAREST_EPOCH_DISTANCE, 22 | "REBASE_CHECK_DISTANT_EPOCH_DISTANCE": REBASE_CHECK_DISTANT_EPOCH_DISTANCE, 23 | "VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS": VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS, 24 | "VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS": VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS, 25 | "PREDICTION_DURATION_IN_SLOTS": PREDICTION_DURATION_IN_SLOTS, 26 | "FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT": FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT, 27 | "NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP": NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP, 28 | } 29 | 30 | 31 | @pytest.fixture(scope="module") 32 | def contract() -> interface.OracleDaemonConfig: 33 | return interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) 34 | 35 | 36 | def test_oracle_daemon_config(contract): 37 | def values_to_int(values) -> list[int]: 38 | return list(map(lambda x: int(str(x), 16), values)) 39 | 40 | contract_values = contract.getList(list(mainnet_config.keys())) 41 | contract_config = dict(zip(mainnet_config.keys(), values_to_int(contract_values))) 42 | 43 | assert mainnet_config == contract_config, "OracleDaemonConfig values are incorrect" 44 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/test_oracle_report_sanity_checker.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface # type: ignore 3 | 4 | from utils.config import ( 5 | contracts, 6 | ORACLE_REPORT_SANITY_CHECKER, 7 | CHURN_VALIDATORS_PER_DAY_LIMIT, 8 | ONE_OFF_CL_BALANCE_DECREASE_BP_LIMIT, 9 | ANNUAL_BALANCE_INCREASE_BP_LIMIT, 10 | SIMULATED_SHARE_RATE_DEVIATION_BP_LIMIT, 11 | MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT, 12 | MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT, 13 | MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT, 14 | REQUEST_TIMESTAMP_MARGIN, 15 | MAX_POSITIVE_TOKEN_REBASE, 16 | ) 17 | 18 | # Source of truth: https://hackmd.io/pdix1r4yR46fXUqiHaNKyw?view 19 | report_limits = { 20 | "churnValidatorsPerDayLimit": CHURN_VALIDATORS_PER_DAY_LIMIT, 21 | "oneOffCLBalanceDecreaseBPLimit": ONE_OFF_CL_BALANCE_DECREASE_BP_LIMIT, 22 | "annualBalanceIncreaseBPLimit": ANNUAL_BALANCE_INCREASE_BP_LIMIT, 23 | "simulatedShareRateDeviationBPLimit": SIMULATED_SHARE_RATE_DEVIATION_BP_LIMIT, 24 | "maxValidatorExitRequestsPerReport": MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT, 25 | "maxAccountingExtraDataListItemsCount": MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT, 26 | "maxNodeOperatorsPerExtraDataItemCount": MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT, 27 | "requestTimestampMargin": REQUEST_TIMESTAMP_MARGIN, 28 | "maxPositiveTokenRebase": MAX_POSITIVE_TOKEN_REBASE, 29 | } 30 | 31 | 32 | @pytest.fixture(scope="module") 33 | def contract() -> interface.OracleReportSanityChecker: 34 | return interface.OracleReportSanityChecker(ORACLE_REPORT_SANITY_CHECKER) 35 | 36 | 37 | def test_links(contract): 38 | assert contract.getLidoLocator() == contracts.lido_locator 39 | 40 | 41 | def test_limits(contract): 42 | assert contract.getMaxPositiveTokenRebase() == report_limits["maxPositiveTokenRebase"] 43 | 44 | assert dict(zip(report_limits.keys(), contract.getOracleReportLimits())) == report_limits 45 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/test_withdrawal_queue.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface, ZERO_ADDRESS, reverts # type: ignore 3 | 4 | from utils.config import ( 5 | contracts, 6 | WITHDRAWAL_QUEUE, 7 | WITHDRAWAL_QUEUE_IMPL, 8 | WQ_ERC721_TOKEN_NAME, 9 | WQ_ERC721_TOKEN_SYMBOL, 10 | WQ_ERC721_TOKEN_BASE_URI, 11 | ) 12 | from utils.evm_script import encode_error 13 | 14 | 15 | @pytest.fixture(scope="module") 16 | def contract() -> interface.WithdrawalQueueERC721: 17 | return interface.WithdrawalQueueERC721(WITHDRAWAL_QUEUE) 18 | 19 | 20 | def test_proxy(contract): 21 | proxy = interface.OssifiableProxy(contract) 22 | assert proxy.proxy__getImplementation() == WITHDRAWAL_QUEUE_IMPL 23 | assert proxy.proxy__getAdmin() == contracts.agent.address 24 | 25 | 26 | def test_versioned(contract): 27 | assert contract.getContractVersion() == 1 28 | 29 | 30 | def test_initialize(contract): 31 | with reverts(encode_error("NonZeroContractVersionOnInit()")): 32 | contract.initialize(contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), {"from": contracts.voting}) 33 | 34 | 35 | def test_petrified(contract): 36 | impl = interface.WithdrawalQueueERC721(WITHDRAWAL_QUEUE_IMPL) 37 | with reverts(encode_error("NonZeroContractVersionOnInit()")): 38 | impl.initialize(contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), {"from": contracts.voting}) 39 | 40 | 41 | def test_pausable_until(contract): 42 | assert contract.isPaused() == False 43 | assert contract.getResumeSinceTimestamp() > 0 44 | 45 | 46 | def test_withdrawal_queue(contract): 47 | assert contract.WSTETH() == contracts.wsteth 48 | assert contract.STETH() == contracts.lido 49 | assert contract.bunkerModeSinceTimestamp() == contract.BUNKER_MODE_DISABLED_TIMESTAMP() 50 | 51 | 52 | def test_withdrawal_queue_erc721(contract): 53 | assert contract.name() == WQ_ERC721_TOKEN_NAME 54 | assert contract.symbol() == WQ_ERC721_TOKEN_SYMBOL 55 | assert contract.getBaseURI() == WQ_ERC721_TOKEN_BASE_URI 56 | assert contract.getNFTDescriptorAddress() == ZERO_ADDRESS 57 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_acceptance/test_withdrawal_vault.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface, reverts # type: ignore 3 | 4 | from utils.config import contracts, WITHDRAWAL_VAULT, WITHDRAWAL_VAULT_IMPL 5 | from utils.evm_script import encode_error 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def contract() -> interface.WithdrawalVault: 10 | return interface.WithdrawalVault(WITHDRAWAL_VAULT) 11 | 12 | 13 | def test_proxy(contract): 14 | proxy = interface.WithdrawalVaultManager(contract) 15 | assert proxy.implementation() == WITHDRAWAL_VAULT_IMPL 16 | assert proxy.proxy_getAdmin() == contracts.voting.address 17 | 18 | 19 | def test_versioned(contract): 20 | assert contract.getContractVersion() == 1 21 | 22 | 23 | def test_initialize(contract): 24 | with reverts(encode_error("NonZeroContractVersionOnInit()")): 25 | contract.initialize({"from": contracts.voting}) 26 | 27 | 28 | def test_petrified(): 29 | impl = interface.WithdrawalVault(WITHDRAWAL_VAULT_IMPL) 30 | with reverts(encode_error("NonZeroContractVersionOnInit()")): 31 | impl.initialize({"from": contracts.voting}) 32 | 33 | 34 | def test_withdrawals_vault(contract): 35 | assert contract.LIDO() == contracts.lido 36 | assert contract.TREASURY() == contracts.agent 37 | assert contract.LIDO() == contracts.lido_locator.lido() 38 | assert contract.TREASURY() == contracts.lido_locator.treasury() 39 | -------------------------------------------------------------------------------- /archive/tests/shapella_upgrade_snapshot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/archive/tests/shapella_upgrade_snapshot/__init__.py -------------------------------------------------------------------------------- /archive/tests/test_2022_06_21_no_auto_execution.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test vote cannot be auto executed if created by 50%+ LDO holder. 3 | """ 4 | 5 | from brownie import accounts 6 | from archive.scripts.upgrade_2022_06_21 import start_vote 7 | from utils.voting import create_vote, bake_vote_items 8 | 9 | 10 | def create_dummy_vote(ldo_holder: str) -> int: 11 | """Creates an empty vote script""" 12 | vote_items = bake_vote_items(vote_desc_items=[], call_script_items=[]) 13 | return create_vote(vote_items, {"from": ldo_holder}, cast_vote=True, executes_if_decided=True)[0] 14 | 15 | 16 | def test_vote(ldo_holder, helpers, dao_voting, ldo_token, dao_token_manager, vote_id_from_env): 17 | # Prepare account with 50+% of LDO 18 | # NB: it has to be TokenManager due to `token_manager.forward` in create_vote 19 | ldo_mega_amount = round(ldo_token.totalSupply() * 1.1) # to constitute at least 50% 20 | ldo_mega_holder = dao_token_manager.address 21 | ldo_token.generateTokens(ldo_mega_holder, ldo_mega_amount, {"from": dao_token_manager.address}) 22 | assert (ldo_token.balanceOf(ldo_mega_holder) / ldo_token.totalSupply()) > 0.5 23 | 24 | # Check a vote is auto-executed 25 | dummy_vote_id = create_dummy_vote(ldo_mega_holder) 26 | assert helpers.is_executed(dummy_vote_id, dao_voting) 27 | 28 | ## 29 | # START VOTE 30 | ## 31 | vote_id = vote_id_from_env if vote_id_from_env is not None else start_vote({"from": ldo_holder}, silent=True)[0] 32 | 33 | helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, topup="0.5 ether") 34 | 35 | # Check a vote isn't auto-executed 36 | dummy_vote_id = create_dummy_vote(ldo_mega_holder) 37 | assert not helpers.is_executed(dummy_vote_id, dao_voting) 38 | -------------------------------------------------------------------------------- /archive/tests/test_2022_07_12.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 12/07/2022. 3 | """ 4 | import pytest 5 | 6 | from scripts.vote_2022_07_12 import start_vote 7 | from utils.test.tx_tracing_helpers import * 8 | from utils.test.event_validators.lido import validate_set_fee_distribution 9 | from utils.config import lido_dao_agent_address 10 | 11 | 12 | def test_vote(helpers, accounts, ldo_holder, dao_voting, vote_id_from_env, bypass_events_decoding, lido): 13 | 14 | assert lido.getFeeDistribution() == (0, 5000, 5000), "unexpected current fee distribution" 15 | assert lido.getInsuranceFund() == lido_dao_agent_address, "unexpected insurance contract address" 16 | assert lido.getTreasury() == lido_dao_agent_address, "unexpected treasury contract address" 17 | 18 | # START VOTE 19 | vote_id: int = vote_id_from_env or start_vote({"from": ldo_holder}, silent=True)[0] 20 | 21 | tx: TransactionReceipt = helpers.execute_vote( 22 | accounts=accounts, vote_id=vote_id, dao_voting=dao_voting 23 | ) 24 | 25 | # validate vote events 26 | assert count_vote_items_by_events(tx, dao_voting) == 1, "Incorrect voting items count" 27 | 28 | # Validate vote items 29 | assert lido.getFeeDistribution() == (5000, 0, 5000) 30 | 31 | display_voting_events(tx) 32 | 33 | if bypass_events_decoding: 34 | return 35 | 36 | evs = group_voting_events(tx) 37 | 38 | validate_set_fee_distribution(evs[0], 5000, 0, 5000), "unexpected new fee distribution" 39 | assert lido.getInsuranceFund() == lido_dao_agent_address, "insurance contract address has changed" 40 | assert lido.getTreasury() == lido_dao_agent_address, "treasury contract address has changed" 41 | -------------------------------------------------------------------------------- /archive/tests/test_2022_11_10.py: -------------------------------------------------------------------------------- 1 | from scripts.vote_2022_11_10 import start_vote 2 | from utils.test.tx_tracing_helpers import * 3 | from utils.test.event_validators.lido import validate_oracle_allowed_beacon_balance_increase_limit 4 | 5 | ORACLE_ADDRESS = '0x442af784A788A5bd6F42A01Ebe9F287a871243fb' 6 | ALLOWED_BEACON_BALANCE_INCREASE_LIMIT = 1750 7 | 8 | def test_vote( 9 | helpers, 10 | bypass_events_decoding, 11 | vote_id_from_env, 12 | accounts, 13 | ldo_holder, 14 | oracle, 15 | dao_voting 16 | ): 17 | assert oracle.address == ORACLE_ADDRESS, "invalid oracle address" 18 | 19 | limit_before = oracle.getAllowedBeaconBalanceAnnualRelativeIncrease() 20 | assert limit_before == 1000, "incorrect current limit" 21 | 22 | # START VOTE 23 | vote_id: int = vote_id_from_env or start_vote({"from": ldo_holder}, silent=True)[0] 24 | 25 | tx: TransactionReceipt = helpers.execute_vote( 26 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, skip_time=3 * 60 * 60 * 24 27 | ) 28 | 29 | limit_after = oracle.getAllowedBeaconBalanceAnnualRelativeIncrease() 30 | assert limit_after == ALLOWED_BEACON_BALANCE_INCREASE_LIMIT, "incorrect limit after upgrade" 31 | 32 | # Validating events 33 | display_voting_events(tx) 34 | 35 | # Validate vote events 36 | if not bypass_events_decoding: 37 | assert count_vote_items_by_events(tx, dao_voting) == 1, "Incorrect voting items count" 38 | 39 | evs = group_voting_events(tx) 40 | 41 | validate_oracle_allowed_beacon_balance_increase_limit(evs[0], ALLOWED_BEACON_BALANCE_INCREASE_LIMIT) 42 | -------------------------------------------------------------------------------- /archive/tests/test_2023_05_03_goerli.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting goerli_change_trp_manager. 3 | 4 | """ 5 | from scripts.vote_2023_05_03_goerli import start_vote 6 | 7 | from brownie.network.transaction import TransactionReceipt 8 | from utils.config import network_name 9 | 10 | from utils.test.tx_tracing_helpers import * 11 | 12 | 13 | def test_vote( 14 | helpers, 15 | accounts, 16 | vote_id_from_env, 17 | ldo_holder, 18 | dao_voting, 19 | trp_factory, 20 | ): 21 | if not network_name() in ("goerli", "goerli-fork"): 22 | return 23 | 24 | expected_manager_before = "0xE80efD4bA1E683DcB681715EEfDFA741B99828e8" 25 | expected_manager_after = "0xde0a8383c0c16c472bdf540e38ad9d85b12eff1e" 26 | 27 | actual_manager_before = trp_factory.manager() 28 | 29 | # check manager before 30 | assert actual_manager_before == expected_manager_before, "Incorrect manager before" 31 | 32 | ## 33 | ## START VOTE 34 | ## 35 | vote_id = vote_id_from_env or start_vote({"from": ldo_holder}, silent=True)[0] 36 | 37 | tx: TransactionReceipt = helpers.execute_vote( 38 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, skip_time=3 * 60 * 60 * 24 39 | ) 40 | 41 | actual_manager_after = trp_factory.manager() 42 | 43 | # check manager before 44 | assert actual_manager_after == expected_manager_after, "Incorrect manager after" -------------------------------------------------------------------------------- /archive/tests/xtest_2021_07_22_cover_refund.py: -------------------------------------------------------------------------------- 1 | from scripts.vote_2021_07_22_cover_refund import (start_vote) 2 | 3 | 4 | def test_set_operators_limit(ldo_holder, helpers, accounts, dao_voting): 5 | refund_address = '0xD089cc83f5B803993E266ACEB929e52A993Ca2C8' 6 | refund_acc = accounts.at(refund_address, force=True) 7 | refund_balance_before = refund_acc.balance() 8 | 9 | (vote_id, _) = start_vote({"from": ldo_holder}, silent=True) 10 | print(f'Vote {vote_id} created') 11 | helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=dao_voting) 12 | print(f'Vote {vote_id} executed') 13 | 14 | refund_balance_after = refund_acc.balance() 15 | 16 | assert refund_balance_after - refund_balance_before == 79837990169609360000 17 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_07_29_fund_easy_audit_and_referral_payout.py: -------------------------------------------------------------------------------- 1 | from brownie import interface 2 | from utils.config import (ldo_token_address) 3 | from scripts.vote_2021_07_29_fund_easy_audit_and_referral_payout import (start_vote) 4 | 5 | 6 | def test_send_funds(ldo_holder, helpers, accounts, dao_voting): 7 | ldo = interface.ERC20(ldo_token_address) 8 | 9 | ops_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 10 | ops_acc = accounts.at(ops_address, force=True) 11 | ops_eth_balance_before = ops_acc.balance() 12 | ops_ldo_balance_before = ldo.balanceOf(ops_address) 13 | 14 | (vote_id, _) = start_vote({"from": ldo_holder}, silent=True) 15 | print(f'Vote {vote_id} created') 16 | helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=dao_voting) 17 | print(f'Vote {vote_id} executed') 18 | 19 | ops_eth_balance_after = ops_acc.balance() 20 | ops_ldo_balance_after = ldo.balanceOf(ops_address) 21 | 22 | assert ops_eth_balance_after - ops_eth_balance_before == 39.9174659279 * 10 ** 18 23 | assert ops_ldo_balance_after - ops_ldo_balance_before == 250_000 * 10 ** 18 24 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_08_05_fund_deversifi_and_curve_rewards.py: -------------------------------------------------------------------------------- 1 | from brownie import interface 2 | from utils.config import (ldo_token_address) 3 | from scripts.vote_2021_08_05_fund_deversifi_and_curve_rewards import (start_vote) 4 | 5 | 6 | def test_send_funds(ldo_holder, helpers, accounts, dao_voting): 7 | ldo = interface.ERC20(ldo_token_address) 8 | 9 | ops_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 10 | ops_ldo_balance_before = ldo.balanceOf(ops_address) 11 | 12 | reward_manager_address = '0x753D5167C31fBEB5b49624314d74A957Eb271709' 13 | reward_manager_ldo_balance_before = ldo.balanceOf(reward_manager_address) 14 | 15 | assert ops_ldo_balance_before == 0 16 | assert reward_manager_ldo_balance_before == 0 17 | 18 | (vote_id, _) = start_vote({"from": ldo_holder}, silent=True) 19 | print(f'Vote {vote_id} created') 20 | helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=dao_voting) 21 | print(f'Vote {vote_id} executed') 22 | 23 | ops_ldo_balance_after = ldo.balanceOf(ops_address) 24 | reward_manager_ldo_balance_after = ldo.balanceOf(reward_manager_address) 25 | 26 | assert ops_ldo_balance_after - ops_ldo_balance_before == 97680 * 10 ** 18 27 | assert reward_manager_ldo_balance_after - reward_manager_ldo_balance_before == 3_750_000 * 10 ** 18 28 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_08_12_fund_sushi_lp_rewards_increase_no_limits.py: -------------------------------------------------------------------------------- 1 | from brownie import interface 2 | from utils.config import (ldo_token_address) 3 | from scripts.vote_2021_08_12_fund_sushi_lp_rewards_increase_no_limits import (start_vote) 4 | 5 | NODE_OPERATORS = [ 6 | { 7 | "id": 0, 8 | "limit": 3500 9 | }, 10 | { 11 | "id": 6, 12 | "limit": 2000 13 | }, 14 | { 15 | "id": 7, 16 | "limit": 2200 17 | }, 18 | { 19 | "id": 8, 20 | "limit": 3001 21 | }, 22 | ] 23 | 24 | 25 | def test_vote(ldo_holder, helpers, accounts, dao_voting, node_operators_registry): 26 | ldo = interface.ERC20(ldo_token_address) 27 | 28 | ops_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 29 | ops_ldo_balance_before = ldo.balanceOf(ops_address) 30 | 31 | assert ops_ldo_balance_before == 0 32 | 33 | (vote_id, _) = start_vote({"from": ldo_holder}, silent=True) 34 | print(f'Vote {vote_id} created') 35 | helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=dao_voting) 36 | print(f'Vote {vote_id} executed') 37 | 38 | ops_ldo_balance_after = ldo.balanceOf(ops_address) 39 | 40 | assert ops_ldo_balance_after - ops_ldo_balance_before == 200_000 * 10 ** 18 41 | 42 | for node_operator in NODE_OPERATORS: 43 | no = node_operators_registry.getNodeOperator(node_operator["id"], True) 44 | assert node_operator["limit"] == no[3] 45 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_09_23.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 09/23/2021. 3 | """ 4 | import pytest 5 | from scripts.vote_2021_09_23 import start_vote 6 | from collections import namedtuple 7 | from utils.config import (ldo_token_address) 8 | from brownie import (interface) 9 | 10 | 11 | @pytest.fixture(scope='module') 12 | def ldo(): 13 | return interface.ERC20(ldo_token_address) 14 | 15 | 16 | NodeOperatorIncLimit = namedtuple( 17 | 'NodeOperatorIncLimit', ['name', 'id', 'limit'], 18 | ) 19 | Payout = namedtuple( 20 | 'Payout', ['address', 'amount', 'reference'] 21 | ) 22 | 23 | NODE_OPERATORS = [ 24 | # name, id, limit 25 | NodeOperatorIncLimit('Everstake', 7, 5000), 26 | NodeOperatorIncLimit('Blockdaemon', 13, 200), 27 | ] 28 | 29 | 30 | def test_2021_09_23(ldo_holder, helpers, accounts, dao_voting, ldo, node_operators_registry): 31 | 32 | stsol_rewards_address = '0xaE49a2C1e2CD3D8f2679a4A49db58983B8de343E' 33 | stsol_rewards_balance_before = ldo.balanceOf(stsol_rewards_address) 34 | 35 | jacob_payout_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 36 | jacob_payout_balance_before = ldo.balanceOf(jacob_payout_address) 37 | 38 | vote_id, _ = start_vote({ 39 | 'from': ldo_holder 40 | }, silent=True) 41 | 42 | helpers.execute_vote( 43 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting 44 | ) 45 | 46 | for node_operator in NODE_OPERATORS: 47 | assert node_operators_registry.getNodeOperator( 48 | node_operator.id, True 49 | )[3] == node_operator.limit, f'Failed on {node_operator.name}' 50 | 51 | stsol_rewards_balance_after = ldo.balanceOf(stsol_rewards_address) 52 | jacob_payout_balance_after = ldo.balanceOf(jacob_payout_address) 53 | 54 | assert stsol_rewards_balance_after - stsol_rewards_balance_before == 400_000 * 10**18 55 | assert jacob_payout_balance_after - jacob_payout_balance_before == 3_500 * 10**18 56 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_09_30.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 09/30/2021. 3 | """ 4 | import pytest 5 | from scripts.vote_2021_09_30 import start_vote 6 | from collections import namedtuple 7 | from utils.config import (ldo_token_address) 8 | from brownie import (interface) 9 | 10 | 11 | @pytest.fixture(scope='module') 12 | def ldo(): 13 | return interface.ERC20(ldo_token_address) 14 | 15 | 16 | NodeOperatorIncLimit = namedtuple( 17 | 'NodeOperatorIncLimit', ['name', 'id', 'limit'], 18 | ) 19 | Payout = namedtuple( 20 | 'Payout', ['address', 'amount', 'reference'] 21 | ) 22 | 23 | NODE_OPERATORS = [ 24 | # name, id, limit 25 | NodeOperatorIncLimit('Blockdaemon', 13, 950), 26 | ] 27 | 28 | 29 | def test_2021_09_30(ldo_holder, helpers, accounts, dao_voting, ldo, node_operators_registry): 30 | referral_payout_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 31 | referral_payout_balance_before = ldo.balanceOf(referral_payout_address) 32 | 33 | vote_id, _ = start_vote({ 34 | 'from': ldo_holder 35 | }, silent=True) 36 | 37 | helpers.execute_vote( 38 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting 39 | ) 40 | 41 | for node_operator in NODE_OPERATORS: 42 | assert node_operators_registry.getNodeOperator( 43 | node_operator.id, True 44 | )[3] == node_operator.limit, f'Failed on {node_operator.name}' 45 | 46 | referral_payout_balance_after = ldo.balanceOf(referral_payout_address) 47 | 48 | assert referral_payout_balance_after - referral_payout_balance_before == 101_133_42 * 10 ** 16 49 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_10_05.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 10/05/2021. 3 | """ 4 | import pytest 5 | from scripts.vote_2021_10_05 import start_vote 6 | from collections import namedtuple 7 | from utils.config import (ldo_token_address) 8 | from brownie import (interface) 9 | 10 | 11 | @pytest.fixture(scope='module') 12 | def ldo(): 13 | return interface.ERC20(ldo_token_address) 14 | 15 | 16 | NodeOperatorIncLimit = namedtuple( 17 | 'NodeOperatorIncLimit', ['name', 'id', 'limit'], 18 | ) 19 | 20 | NODE_OPERATORS = [ 21 | # name, id, limit 22 | NodeOperatorIncLimit('Staking Facilities', 0, 4400), 23 | NodeOperatorIncLimit('Certus One', 1, 1000), 24 | NodeOperatorIncLimit('p2p', 2, 5265), 25 | NodeOperatorIncLimit('Chorus One', 3, 5000), 26 | NodeOperatorIncLimit('stakefish', 4, 5265), 27 | NodeOperatorIncLimit('Blockscape', 5, 5265), 28 | NodeOperatorIncLimit('DSRV', 6, 4000), 29 | NodeOperatorIncLimit('Everstake', 7, 3000), 30 | NodeOperatorIncLimit('SkillZ', 8, 5265), 31 | NodeOperatorIncLimit('RockX', 9, 684), 32 | NodeOperatorIncLimit('Figment', 10, 683), 33 | NodeOperatorIncLimit('Allnodes', 11, 683), 34 | NodeOperatorIncLimit('Anyblock Analytics', 12, 683), 35 | NodeOperatorIncLimit('Blockdaemon', 13, 683), 36 | 37 | ] 38 | 39 | 40 | def test_2021_10_05(ldo_holder, helpers, accounts, dao_voting, ldo, node_operators_registry): 41 | vote_id, _ = start_vote({ 42 | 'from': ldo_holder 43 | }, silent=True) 44 | 45 | helpers.execute_vote( 46 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting 47 | ) 48 | 49 | for node_operator in NODE_OPERATORS: 50 | nop = node_operators_registry.getNodeOperator( 51 | node_operator.id, True 52 | ) 53 | limit = nop[3] 54 | used = nop[6] 55 | assert limit == node_operator.limit, f'Failed on {node_operator.name}' 56 | assert used == limit, f'Not used on {node_operator.name}' 57 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_10_14.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 10/14/2021. 3 | """ 4 | import pytest 5 | from scripts.vote_2021_10_14 import start_vote 6 | from collections import namedtuple 7 | from utils.config import (ldo_token_address) 8 | from brownie import (interface) 9 | 10 | 11 | @pytest.fixture(scope='module') 12 | def ldo(): 13 | return interface.ERC20(ldo_token_address) 14 | 15 | 16 | Payout = namedtuple( 17 | 'Payout', ['address', 'amount', 'reference'] 18 | ) 19 | 20 | 21 | def test_2021_10_14(ldo_holder, helpers, accounts, dao_voting, ldo): 22 | referral_payout_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 23 | referral_payout_balance_before = ldo.balanceOf(referral_payout_address) 24 | sushi_address = '0xE5576eB1dD4aA524D67Cf9a32C8742540252b6F4' 25 | sushi_balance_before = ldo.balanceOf(sushi_address) 26 | 27 | vote_id, _ = start_vote({ 28 | 'from': ldo_holder 29 | }, silent=True) 30 | 31 | helpers.execute_vote( 32 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting 33 | ) 34 | 35 | referral_payout_balance_after = ldo.balanceOf(referral_payout_address) 36 | sushi_balance_after = ldo.balanceOf(sushi_address) 37 | 38 | assert referral_payout_balance_after - referral_payout_balance_before == 303_142_5 * 10 ** 17 39 | assert sushi_balance_after - sushi_balance_before == 200_000 * 10 ** 18 40 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_10_21.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 10/21/2021. 3 | """ 4 | from collections import namedtuple 5 | 6 | from scripts.vote_2021_10_21 import start_vote 7 | 8 | Payout = namedtuple( 9 | 'Payout', ['address', 'amount'] 10 | ) 11 | 12 | referral_payout = Payout( 13 | address='0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb', 14 | amount=5_500 * 10 ** 18 15 | ) 16 | 17 | one_inch_payout = Payout( 18 | address='0xf5436129cf9d8fa2a1cb6e591347155276550635', 19 | amount=200_000 * 10 ** 18 20 | ) 21 | 22 | 23 | def test_2021_10_21(ldo_holder, helpers, accounts, dao_voting, ldo_token): 24 | referral_payout_balance_before = ldo_token.balanceOf( 25 | referral_payout.address 26 | ) 27 | one_inch_balance_before = ldo_token.balanceOf( 28 | one_inch_payout.address 29 | ) 30 | 31 | vote_id, _ = start_vote({ 32 | 'from': ldo_holder 33 | }, silent=True) 34 | 35 | helpers.execute_vote( 36 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting 37 | ) 38 | 39 | referral_payout_balance_after = ldo_token.balanceOf( 40 | referral_payout.address 41 | ) 42 | one_inch_balance_after = ldo_token.balanceOf( 43 | one_inch_payout.address 44 | ) 45 | 46 | assert referral_payout_balance_after - referral_payout_balance_before == referral_payout.amount 47 | assert one_inch_balance_after - one_inch_balance_before == one_inch_payout.amount 48 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_10_28.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 10/28/2021. 3 | """ 4 | import pytest 5 | from collections import namedtuple 6 | 7 | from scripts.vote_2021_10_28 import start_vote 8 | 9 | Payout = namedtuple( 10 | 'Payout', ['address', 'amount'] 11 | ) 12 | 13 | referral_payout = Payout( 14 | address='0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb', 15 | amount=138_162_5642 * 10 ** 14 16 | ) 17 | 18 | 19 | @pytest.fixture(scope='module') 20 | def deposit_security_module(interface): 21 | return interface.DepositSecurityModule('0xDb149235B6F40dC08810AA69869783Be101790e7') 22 | 23 | 24 | def test_2021_10_28(ldo_holder, helpers, accounts, dao_voting, ldo_token, deposit_security_module): 25 | referral_payout_balance_before = ldo_token.balanceOf( 26 | referral_payout.address 27 | ) 28 | assert deposit_security_module.isPaused() == True 29 | 30 | vote_id, _ = start_vote({ 31 | 'from': ldo_holder 32 | }, silent=True) 33 | 34 | helpers.execute_vote( 35 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting 36 | ) 37 | 38 | referral_payout_balance_after = ldo_token.balanceOf( 39 | referral_payout.address 40 | ) 41 | 42 | assert deposit_security_module.isPaused() == False 43 | assert referral_payout_balance_after - referral_payout_balance_before == referral_payout.amount 44 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_11_04.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 11/04/2021. 3 | """ 4 | import pytest 5 | from collections import namedtuple 6 | 7 | from scripts.vote_2021_11_04 import start_vote 8 | 9 | Payout = namedtuple( 10 | 'Payout', ['address', 'amount'] 11 | ) 12 | 13 | payout_curve = Payout( 14 | address='0x753D5167C31fBEB5b49624314d74A957Eb271709', 15 | amount=3_550_000 * 10 ** 18 16 | ) 17 | payout_balancer = Payout( 18 | address='0x1dD909cDdF3dbe61aC08112dC0Fdf2Ab949f79D8', 19 | amount=300_000 * 10 ** 18 20 | ) 21 | payout_stsol = Payout( 22 | address='0xaE49a2C1e2CD3D8f2679a4A49db58983B8de343E', 23 | amount=400_000 * 10 ** 18 24 | ) 25 | 26 | 27 | def test_2021_11_04(ldo_holder, helpers, accounts, dao_voting, ldo_token, deposit_security_module): 28 | curve_balance_before = ldo_token.balanceOf(payout_curve.address) 29 | balancer_balance_before = ldo_token.balanceOf(payout_balancer.address) 30 | stsol_balance_before = ldo_token.balanceOf(payout_stsol.address) 31 | 32 | vote_id, _ = start_vote({ 33 | 'from': ldo_holder 34 | }, silent=True) 35 | 36 | helpers.execute_vote( 37 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting 38 | ) 39 | 40 | curve_balance_after = ldo_token.balanceOf(payout_curve.address) 41 | balancer_balance_after = ldo_token.balanceOf(payout_balancer.address) 42 | stsol_balance_after = ldo_token.balanceOf(payout_stsol.address) 43 | 44 | assert curve_balance_after - curve_balance_before == payout_curve.amount 45 | assert balancer_balance_after - balancer_balance_before == payout_balancer.amount 46 | assert stsol_balance_after - stsol_balance_before == payout_stsol.amount 47 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_12_02.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 02/12/2021. 3 | """ 4 | 5 | from scripts.vote_2021_12_02 import start_vote 6 | 7 | EVM_SCRIPT_EXECUTOR_ADDRESS = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" 8 | 9 | 10 | def test_2021_12_02( 11 | helpers, acl, finance, node_operators_registry, accounts, ldo_holder, dao_voting 12 | ): 13 | assert not acl.hasPermission( 14 | EVM_SCRIPT_EXECUTOR_ADDRESS, finance, finance.CREATE_PAYMENTS_ROLE() 15 | ) 16 | assert not acl.hasPermission( 17 | EVM_SCRIPT_EXECUTOR_ADDRESS, 18 | node_operators_registry, 19 | node_operators_registry.SET_NODE_OPERATOR_LIMIT_ROLE(), 20 | ) 21 | vote_id, _ = start_vote({"from": ldo_holder}, silent=True) 22 | helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=dao_voting) 23 | 24 | assert acl.hasPermission( 25 | EVM_SCRIPT_EXECUTOR_ADDRESS, finance, finance.CREATE_PAYMENTS_ROLE() 26 | ) 27 | assert acl.hasPermission( 28 | EVM_SCRIPT_EXECUTOR_ADDRESS, 29 | node_operators_registry, 30 | node_operators_registry.SET_NODE_OPERATOR_LIMIT_ROLE(), 31 | ) 32 | -------------------------------------------------------------------------------- /archive/tests/xtest_2021_12_16.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 16/12/2021. 3 | """ 4 | 5 | from scripts.vote_2021_12_16 import start_vote 6 | from tx_tracing_helpers import * 7 | 8 | 9 | def test_2021_12_16( 10 | helpers, accounts, ldo_holder, dao_voting, lido 11 | ): 12 | aragonAgentAddr = '0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c' 13 | 14 | totalSharesBefore = lido.getTotalShares() 15 | sharesAragonAgentBefore = lido.sharesOf(aragonAgentAddr) 16 | 17 | sharesToBurn = 32145684728326685744 18 | 19 | vote_id, _ = start_vote({'from': ldo_holder}, silent=True) 20 | tx: TransactionReceipt = helpers.execute_vote( 21 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting 22 | ) 23 | 24 | display_voting_events(tx) 25 | 26 | ### validate vote events 27 | assert count_vote_items_by_events(tx) == 1, "Incorrect voting items count" 28 | 29 | # check burned shares 30 | totalSharesAfter = lido.getTotalShares() 31 | sharesAragonAgentAfter = lido.sharesOf(aragonAgentAddr) 32 | 33 | assert totalSharesBefore - totalSharesAfter == sharesToBurn 34 | assert sharesAragonAgentBefore - sharesAragonAgentAfter == sharesToBurn 35 | -------------------------------------------------------------------------------- /archive/tests/xtest_2022_02_03.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 03/02/2022. 3 | """ 4 | 5 | from scripts.vote_2022_02_03 import start_vote 6 | from tx_tracing_helpers import * 7 | 8 | from event_validators.payout import Payout, validate_payout_event 9 | 10 | dao_agent_address = '0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c' 11 | finance_multisig_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 12 | lido_dao_token = '0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32' 13 | 14 | referral_payout = Payout( 15 | token_addr=lido_dao_token, 16 | from_addr=dao_agent_address, 17 | to_addr=finance_multisig_address, 18 | amount=180_278 * (10 ** 18) 19 | ) 20 | 21 | 22 | def test_2022_02_03( 23 | helpers, accounts, ldo_holder, dao_voting, ldo_token, 24 | vote_id_from_env, bypass_events_decoding 25 | ): 26 | multisig_balance_before = ldo_token.balanceOf(finance_multisig_address) 27 | dao_balance_before = ldo_token.balanceOf(dao_agent_address) 28 | 29 | ## 30 | # START VOTE 31 | ## 32 | vote_id = vote_id_from_env or start_vote({'from': ldo_holder}, silent=True)[0] 33 | 34 | tx: TransactionReceipt = helpers.execute_vote( 35 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, topup='0.37 ether' 36 | ) 37 | 38 | multisig_balance_after = ldo_token.balanceOf(finance_multisig_address) 39 | dao_balance_after = ldo_token.balanceOf(dao_agent_address) 40 | 41 | assert multisig_balance_after - multisig_balance_before == referral_payout.amount 42 | assert dao_balance_before - dao_balance_after == referral_payout.amount 43 | 44 | # validate vote events 45 | assert count_vote_items_by_events(tx) == 1, "Incorrect voting items count" 46 | 47 | display_voting_events(tx) 48 | 49 | if bypass_events_decoding: 50 | return 51 | 52 | evs = group_voting_events(tx) 53 | 54 | # asserts on vote item 1 55 | validate_payout_event(evs[0], referral_payout) 56 | -------------------------------------------------------------------------------- /archive/tests/xtest_2022_02_04.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 04/02/2022. 3 | """ 4 | 5 | from scripts.vote_2022_02_04 import start_vote 6 | from tx_tracing_helpers import * 7 | 8 | from event_validators.payout import Payout, validate_payout_event 9 | 10 | dao_agent_address = '0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c' 11 | finance_multisig_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 12 | lido_dao_token = '0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32' 13 | 14 | referral_payout = Payout( 15 | token_addr=lido_dao_token, 16 | from_addr=dao_agent_address, 17 | to_addr=finance_multisig_address, 18 | amount=180_278 * (10 ** 18) 19 | ) 20 | 21 | 22 | def test_2022_02_04( 23 | helpers, accounts, ldo_holder, dao_voting, ldo_token, 24 | vote_id_from_env, bypass_events_decoding 25 | ): 26 | multisig_balance_before = ldo_token.balanceOf(finance_multisig_address) 27 | dao_balance_before = ldo_token.balanceOf(dao_agent_address) 28 | 29 | ## 30 | # START VOTE 31 | ## 32 | vote_id = vote_id_from_env or start_vote({'from': ldo_holder}, silent=True)[0] 33 | 34 | tx: TransactionReceipt = helpers.execute_vote( 35 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, topup='0.37 ether' 36 | ) 37 | 38 | multisig_balance_after = ldo_token.balanceOf(finance_multisig_address) 39 | dao_balance_after = ldo_token.balanceOf(dao_agent_address) 40 | 41 | assert multisig_balance_after - multisig_balance_before == referral_payout.amount 42 | assert dao_balance_before - dao_balance_after == referral_payout.amount 43 | 44 | # validate vote events 45 | assert count_vote_items_by_events(tx) == 1, "Incorrect voting items count" 46 | 47 | display_voting_events(tx) 48 | 49 | if bypass_events_decoding: 50 | return 51 | 52 | evs = group_voting_events(tx) 53 | 54 | # asserts on vote item 1 55 | validate_payout_event(evs[0], referral_payout) 56 | -------------------------------------------------------------------------------- /archive/tests/xtest_2022_02_07.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 07/02/2022. 3 | """ 4 | 5 | from scripts.vote_2022_02_07 import start_vote 6 | from tx_tracing_helpers import * 7 | 8 | from event_validators.payout import Payout, validate_payout_event 9 | 10 | dao_agent_address = '0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c' 11 | finance_multisig_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 12 | lido_dao_token = '0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32' 13 | 14 | referral_payout = Payout( 15 | token_addr=lido_dao_token, 16 | from_addr=dao_agent_address, 17 | to_addr=finance_multisig_address, 18 | amount=180_278 * (10 ** 18) 19 | ) 20 | 21 | 22 | def test_2022_02_07( 23 | helpers, accounts, ldo_holder, dao_voting, ldo_token, 24 | vote_id_from_env, bypass_events_decoding 25 | ): 26 | multisig_balance_before = ldo_token.balanceOf(finance_multisig_address) 27 | dao_balance_before = ldo_token.balanceOf(dao_agent_address) 28 | 29 | ## 30 | # START VOTE 31 | ## 32 | vote_id = vote_id_from_env or start_vote({'from': ldo_holder}, silent=True)[0] 33 | 34 | tx: TransactionReceipt = helpers.execute_vote( 35 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, topup='0.37 ether' 36 | ) 37 | 38 | multisig_balance_after = ldo_token.balanceOf(finance_multisig_address) 39 | dao_balance_after = ldo_token.balanceOf(dao_agent_address) 40 | 41 | assert multisig_balance_after - multisig_balance_before == referral_payout.amount 42 | assert dao_balance_before - dao_balance_after == referral_payout.amount 43 | 44 | # validate vote events 45 | assert count_vote_items_by_events(tx) == 1, "Incorrect voting items count" 46 | 47 | display_voting_events(tx) 48 | 49 | if bypass_events_decoding: 50 | return 51 | 52 | evs = group_voting_events(tx) 53 | 54 | # asserts on vote item 1 55 | validate_payout_event(evs[0], referral_payout) 56 | -------------------------------------------------------------------------------- /archive/tests/xtest_2022_03_03.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 03/03/2022. 3 | """ 4 | 5 | from event_validators.payout import Payout, validate_payout_event 6 | 7 | from scripts.vote_2022_03_03 import start_vote 8 | from tx_tracing_helpers import * 9 | 10 | dao_agent_address = '0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c' 11 | finance_multisig_address = '0x48F300bD3C52c7dA6aAbDE4B683dEB27d38B9ABb' 12 | lido_dao_token = '0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32' 13 | 14 | referral_payout = Payout( 15 | token_addr=lido_dao_token, 16 | from_addr=dao_agent_address, 17 | to_addr=finance_multisig_address, 18 | amount=412_082 * (10 ** 18) 19 | ) 20 | 21 | 22 | def test_2022_03_03( 23 | helpers, accounts, ldo_holder, dao_voting, ldo_token, 24 | vote_id_from_env, bypass_events_decoding 25 | ): 26 | multisig_balance_before = ldo_token.balanceOf(finance_multisig_address) 27 | dao_balance_before = ldo_token.balanceOf(dao_agent_address) 28 | 29 | ## 30 | ## START VOTE 31 | ## 32 | vote_id = vote_id_from_env or start_vote({'from': ldo_holder}, silent=True)[0] 33 | 34 | tx: TransactionReceipt = helpers.execute_vote( 35 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, topup='0.37 ether' 36 | ) 37 | 38 | multisig_balance_after = ldo_token.balanceOf(finance_multisig_address) 39 | dao_balance_after = ldo_token.balanceOf(dao_agent_address) 40 | 41 | assert multisig_balance_after - multisig_balance_before == referral_payout.amount 42 | assert dao_balance_before - dao_balance_after == referral_payout.amount 43 | 44 | ### validate vote events 45 | assert count_vote_items_by_events(tx) == 1, "Incorrect voting items count" 46 | 47 | display_voting_events(tx) 48 | 49 | if bypass_events_decoding: 50 | return 51 | 52 | evs = group_voting_events(tx) 53 | 54 | # asserts on vote item 1 55 | validate_payout_event(evs[0], referral_payout) 56 | -------------------------------------------------------------------------------- /archive/tests/xtest_2022_03_22.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 22/03/2022. 3 | """ 4 | from scripts.vote_2022_03_22 import start_vote 5 | from tx_tracing_helpers import * 6 | 7 | ldo_amount: int = 3_691_500 * 10 ** 18 8 | source_address: str = '0x48Acf41D10a063f9A6B718B9AAd2e2fF5B319Ca2' 9 | 10 | lido_dao_token = '0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32' 11 | 12 | 13 | def test_ldo_recover( 14 | helpers, accounts, ldo_holder, dao_voting, 15 | ldo_token, dao_token_manager, acl, 16 | vote_id_from_env, bypass_events_decoding 17 | ): 18 | total_supply_before = ldo_token.totalSupply() 19 | source_balance_before = ldo_token.balanceOf(source_address) 20 | token_manager_balance_before = ldo_token.balanceOf(dao_token_manager) 21 | 22 | assert not acl.hasPermission(dao_voting, dao_token_manager, dao_token_manager.BURN_ROLE()) 23 | 24 | ## 25 | ## START VOTE 26 | ## 27 | vote_id = vote_id_from_env or start_vote({'from': ldo_holder}, silent=True)[0] 28 | 29 | tx: TransactionReceipt = helpers.execute_vote( 30 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, skip_time=3 * 60 * 60 * 24 31 | ) 32 | 33 | total_supply_after = ldo_token.totalSupply() 34 | source_balance_after = ldo_token.balanceOf(source_address) 35 | token_manager_balance_after = ldo_token.balanceOf(dao_token_manager) 36 | 37 | assert total_supply_before == (total_supply_after + ldo_amount), "Total supply changed" 38 | assert source_balance_before == source_balance_after + ldo_amount, "Incorrect LDO amount" 39 | assert token_manager_balance_before == token_manager_balance_after, "Incorrect LDO amount" 40 | 41 | assert not acl.hasPermission(dao_voting, dao_token_manager, dao_token_manager.BURN_ROLE()) 42 | 43 | ### validate vote events 44 | assert count_vote_items_by_events(tx) == 3, "Incorrect voting items count" 45 | 46 | display_voting_events(tx) 47 | 48 | if bypass_events_decoding: 49 | return 50 | -------------------------------------------------------------------------------- /archive/tests/xtest_2022_04_16.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 16/04/2022. 3 | """ 4 | 5 | from event_validators.unpause import ( 6 | validate_unpause_event 7 | ) 8 | 9 | from tx_tracing_helpers import * 10 | 11 | from archive.scripts.vote_2022_04_16 import start_vote 12 | 13 | 14 | def test_2022_04_16(ldo_holder, helpers, accounts, dao_voting, deposit_security_module, vote_id_from_env, bypass_events_decoding): 15 | assert deposit_security_module.isPaused() 16 | 17 | # 18 | # START VOTE 19 | # 20 | vote_id = vote_id_from_env or start_vote({'from': ldo_holder}, silent=True)[0] 21 | 22 | tx: TransactionReceipt = helpers.execute_vote( 23 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, topup='0.5 ether' 24 | ) 25 | 26 | assert not deposit_security_module.isPaused() 27 | 28 | # validate vote events 29 | assert count_vote_items_by_events(tx, dao_voting) == 1, "Incorrect voting items count" 30 | 31 | display_voting_events(tx) 32 | 33 | if bypass_events_decoding: 34 | return 35 | 36 | evs = group_voting_events(tx) 37 | 38 | # asserts on vote item 1 39 | validate_unpause_event(evs[0]) 40 | -------------------------------------------------------------------------------- /archive/tests/xtest_2022_04_19.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for voting 19/04/2022. 3 | """ 4 | 5 | from event_validators.payout import ( 6 | Payout, 7 | validate_token_payout_event 8 | ) 9 | 10 | from archive.scripts.vote_2022_04_19 import start_vote 11 | from tx_tracing_helpers import * 12 | 13 | rcc_multisig_address = '0xDE06d17Db9295Fa8c4082D4f73Ff81592A3aC437' 14 | dao_agent_address = '0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c' 15 | steth_address = '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84' 16 | 17 | 18 | def steth_balance_checker(lhs_value: int, rhs_value: int): 19 | assert (lhs_value + 5) // 10 == (rhs_value + 5) // 10 20 | 21 | 22 | fund_payout = Payout( 23 | token_addr=steth_address, 24 | from_addr=dao_agent_address, 25 | to_addr=rcc_multisig_address, 26 | amount=363_410_003_370_231_400_000 27 | ) 28 | 29 | 30 | def test_2022_04_19( 31 | helpers, accounts, ldo_holder, dao_voting, lido, 32 | vote_id_from_env, bypass_events_decoding 33 | ): 34 | rcc_multisig_balance_before = lido.balanceOf(rcc_multisig_address) 35 | dao_balance_before = lido.balanceOf(dao_agent_address) 36 | 37 | # 38 | # START VOTE 39 | # 40 | vote_id = vote_id_from_env or start_vote({'from': ldo_holder}, silent=True)[0] 41 | 42 | tx: TransactionReceipt = helpers.execute_vote( 43 | vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, topup='0.5 ether' 44 | ) 45 | 46 | rcc_multisig_balance_after = lido.balanceOf(rcc_multisig_address) 47 | dao_balance_after = lido.balanceOf(dao_agent_address) 48 | 49 | steth_balance_checker(rcc_multisig_balance_after - rcc_multisig_balance_before, fund_payout.amount) 50 | steth_balance_checker(dao_balance_before - dao_balance_after, fund_payout.amount) 51 | 52 | # validate vote events 53 | assert count_vote_items_by_events(tx, dao_voting) == 1, "Incorrect voting items count" 54 | 55 | display_voting_events(tx) 56 | 57 | if bypass_events_decoding: 58 | return 59 | 60 | evs = group_voting_events(tx) 61 | 62 | # asserts on vote item 1 63 | validate_token_payout_event(evs[0], fund_payout) 64 | -------------------------------------------------------------------------------- /archive/tests/xtest_set_node_operators_limit.py: -------------------------------------------------------------------------------- 1 | from pytest import raises 2 | from scripts.set_node_operators_limit import (set_node_operator_staking_limits, 3 | validate_data) 4 | 5 | NODE_OPERATORS = [ 6 | { 7 | "id": 0, 8 | "limit": 0 9 | }, 10 | { 11 | "id": 3, 12 | "limit": 10 13 | }, 14 | { 15 | "id": 5, 16 | "limit": 10 17 | }, 18 | { 19 | "id": 6, 20 | "limit": 10 21 | }, 22 | ] 23 | 24 | 25 | def test_set_operators_limit(ldo_holder, helpers, accounts, dao_voting, 26 | node_operators_registry): 27 | (vote_id, _) = set_node_operator_staking_limits({"from": ldo_holder}, 28 | NODE_OPERATORS) 29 | print(f'Vote {vote_id} created') 30 | helpers.execute_vote(vote_id=vote_id, 31 | accounts=accounts, 32 | dao_voting=dao_voting) 33 | print(f'Vote {vote_id} executed') 34 | 35 | for node_operator in NODE_OPERATORS: 36 | no = node_operators_registry.getNodeOperator(node_operator["id"], True) 37 | assert node_operator["limit"] == no[3] 38 | 39 | 40 | def test_validator(): 41 | validate_data(NODE_OPERATORS) 42 | 43 | 44 | def test_validator_failed_on_duplicate(): 45 | with raises(Exception): 46 | validate_data((NODE_OPERATORS[0], NODE_OPERATORS[0])) 47 | 48 | 49 | def test_validator_failed_on_wrong_id(): 50 | with raises(Exception): 51 | validate_data(({"id": -1, "limit": 20})) 52 | 53 | 54 | def test_validator_failed_on_wrong_limit(): 55 | with raises(Exception): 56 | validate_data(({"id": 1, "limit": -20})) 57 | 58 | 59 | def test_validator_failed_on_not_existing_node_operator(): 60 | with raises(Exception): 61 | validate_data(({"id": 1000000, "limit": 20})) 62 | -------------------------------------------------------------------------------- /assets/voting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/assets/voting.png -------------------------------------------------------------------------------- /brownie-config.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | # we're using 4.0.0 since we don't need proxy beacon logic from 4.1.0 3 | - OpenZeppelin/openzeppelin-contracts@4.0.0 4 | 5 | networks: 6 | default: mainnet-fork 7 | development: 8 | priority_fee: auto 9 | live: 10 | priority_fee: auto 11 | 12 | autofetch_sources: true 13 | 14 | hypothesis: 15 | max_examples: 10 16 | -------------------------------------------------------------------------------- /contracts/EtherFunder.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.7 2 | # @author psirex 3 | # @licence MIT 4 | 5 | @payable 6 | @external 7 | def __init__(ether_recipient: address): 8 | selfdestruct(ether_recipient) 9 | -------------------------------------------------------------------------------- /contracts/MockCallTarget.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.7 2 | 3 | called: public(bool) 4 | 5 | @external 6 | def perform_call(): 7 | assert not self.called 8 | self.called = True 9 | -------------------------------------------------------------------------------- /contracts/MockHashConsensus.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.7 2 | # @licence MIT 3 | 4 | SLOTS_PER_EPOCH: immutable(uint64) 5 | SECONDS_PER_SLOT: immutable(uint64) 6 | GENESIS_TIME: immutable(uint64) 7 | INITIAL_REF_SLOT: immutable(uint256) 8 | 9 | 10 | @external 11 | def __init__(slots_per_epoch: uint64, seconds_per_slot: uint64, genesis_time: uint64, initial_ref_slot: uint256): 12 | SLOTS_PER_EPOCH = slots_per_epoch 13 | SECONDS_PER_SLOT = seconds_per_slot 14 | GENESIS_TIME = genesis_time 15 | INITIAL_REF_SLOT = initial_ref_slot 16 | 17 | 18 | @external 19 | def getChainConfig() -> (uint64, uint64, uint64): 20 | return SLOTS_PER_EPOCH, SECONDS_PER_SLOT, GENESIS_TIME 21 | 22 | 23 | @external 24 | def getInitialRefSlot() -> uint256: 25 | return INITIAL_REF_SLOT 26 | -------------------------------------------------------------------------------- /contracts/OpStackTokenRatePusherWithSomeErrorStub.sol: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Lido 2 | // SPDX-License-Identifier: GPL-3.0 3 | 4 | pragma solidity 0.8.10; 5 | 6 | interface ITokenRatePusher { 7 | function pushTokenRate() external; 8 | } 9 | 10 | interface IERC165 { 11 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 12 | } 13 | 14 | abstract contract ERC165 is IERC165 { 15 | function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { 16 | return interfaceId == type(IERC165).interfaceId; 17 | } 18 | } 19 | 20 | /// @dev For testing purposes. 21 | contract OpStackTokenRatePusherWithSomeErrorStub is ERC165, ITokenRatePusher { 22 | 23 | error SomeError(); 24 | 25 | function pushTokenRate() pure external { 26 | revert SomeError(); 27 | } 28 | 29 | /// @inheritdoc ERC165 30 | function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { 31 | return ( 32 | _interfaceId == type(ITokenRatePusher).interfaceId 33 | || super.supportsInterface(_interfaceId) 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/RewardsManagerTokensRecoverer.vy: -------------------------------------------------------------------------------- 1 | # @version 0.3.7 2 | # @notice A contract to recover tokens from RewardsManager contract 3 | # @license MIT 4 | 5 | interface IRewardsManager: 6 | def owner() -> address: view 7 | def recover_erc20(token: address, amount: uint256, recipient: address): nonpayable 8 | def transfer_ownership(_to: address): nonpayable 9 | 10 | interface IERC20: 11 | def balanceOf(account: address) -> uint256: view 12 | 13 | AGENT: immutable(address) 14 | 15 | @external 16 | def __init__(agent: address): 17 | AGENT = agent 18 | 19 | @pure 20 | @external 21 | def agent() -> address: 22 | return AGENT 23 | 24 | @external 25 | def recover(manager: address, token: address, amount: uint256 = MAX_UINT256)-> uint256: 26 | recovered_amount: uint256 = 0 27 | is_owner: bool = IRewardsManager(manager).owner() == self 28 | current_balance: uint256 = IERC20(token).balanceOf(manager) 29 | amount_to_recover: uint256 = min(amount, current_balance) 30 | 31 | if is_owner and amount_to_recover > 0: 32 | IRewardsManager(manager).recover_erc20(token, amount_to_recover, AGENT) 33 | recovered_amount = amount_to_recover 34 | 35 | if is_owner: 36 | IRewardsManager(manager).transfer_ownership(AGENT) 37 | 38 | log Recover(msg.sender, manager, token, amount, recovered_amount) 39 | return recovered_amount 40 | 41 | event Recover: 42 | sender: indexed(address) 43 | manager: indexed(address) 44 | token: indexed(address) 45 | amount: uint256 46 | recovered_amount: uint256 -------------------------------------------------------------------------------- /ganache.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | npx ganache-cli "$@" 3 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | hardhat: { 4 | hardfork: "cancun", 5 | chains: { 6 | 560048: { 7 | hardforkHistory: { 8 | cancun: 0, 9 | }, 10 | } 11 | } 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /interfaces/0xsplits/SplitWallet.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"split","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ReceiveETH","type":"event"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sendERC20ToMain","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sendETHToMain","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"splitMain","outputs":[{"internalType":"contract ISplitMain","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/ActivateNodeOperators.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_trustedCaller","type":"address"},{"internalType":"address","name":"_nodeOperatorsRegistry","type":"address"},{"internalType":"address","name":"_acl","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"acl","outputs":[{"internalType":"contract IACL","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"createEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"decodeEVMScriptCallData","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"internalType":"address","name":"managerAddress","type":"address"}],"internalType":"struct ActivateNodeOperators.ActivateNodeOperatorInput[]","name":"","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"nodeOperatorsRegistry","outputs":[{"internalType":"contract INodeOperatorsRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedCaller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/AddNodeOperators.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_trustedCaller","type":"address"},{"internalType":"address","name":"_nodeOperatorsRegistry","type":"address"},{"internalType":"address","name":"_acl","type":"address"},{"internalType":"address","name":"_lido","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"acl","outputs":[{"internalType":"contract IACL","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"createEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"decodeEVMScriptCallData","outputs":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"rewardAddress","type":"address"},{"internalType":"address","name":"managerAddress","type":"address"}],"internalType":"struct AddNodeOperators.AddNodeOperatorInput[]","name":"nodeOperators","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"lido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nodeOperatorsRegistry","outputs":[{"internalType":"contract INodeOperatorsRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedCaller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/AddRewardProgram.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_trustedCaller", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "address", 11 | "name": "_rewardProgramsRegistry", 12 | "type": "address" 13 | } 14 | ], 15 | "stateMutability": "nonpayable", 16 | "type": "constructor" 17 | }, 18 | { 19 | "inputs": [ 20 | { 21 | "internalType": "address", 22 | "name": "_creator", 23 | "type": "address" 24 | }, 25 | { 26 | "internalType": "bytes", 27 | "name": "_evmScriptCallData", 28 | "type": "bytes" 29 | } 30 | ], 31 | "name": "createEVMScript", 32 | "outputs": [ 33 | { 34 | "internalType": "bytes", 35 | "name": "", 36 | "type": "bytes" 37 | } 38 | ], 39 | "stateMutability": "view", 40 | "type": "function" 41 | }, 42 | { 43 | "inputs": [ 44 | { 45 | "internalType": "bytes", 46 | "name": "_evmScriptCallData", 47 | "type": "bytes" 48 | } 49 | ], 50 | "name": "decodeEVMScriptCallData", 51 | "outputs": [ 52 | { 53 | "internalType": "address", 54 | "name": "", 55 | "type": "address" 56 | }, 57 | { 58 | "internalType": "string", 59 | "name": "", 60 | "type": "string" 61 | } 62 | ], 63 | "stateMutability": "pure", 64 | "type": "function" 65 | }, 66 | { 67 | "inputs": [], 68 | "name": "rewardProgramsRegistry", 69 | "outputs": [ 70 | { 71 | "internalType": "contract RewardProgramsRegistry", 72 | "name": "", 73 | "type": "address" 74 | } 75 | ], 76 | "stateMutability": "view", 77 | "type": "function" 78 | }, 79 | { 80 | "inputs": [], 81 | "name": "trustedCaller", 82 | "outputs": [ 83 | { 84 | "internalType": "address", 85 | "name": "", 86 | "type": "address" 87 | } 88 | ], 89 | "stateMutability": "view", 90 | "type": "function" 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /interfaces/AnchorVaultProxy.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"implementation","type":"address"},{"internalType":"address","name":"admin","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"proxy_changeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proxy_getAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxy_getIsOssified","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"setupCalldata","type":"bytes"}],"name":"proxy_upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] -------------------------------------------------------------------------------- /interfaces/AppProxyUpgradeable.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"proxyType","outputs":[{"name":"proxyTypeId","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"isDepositable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"implementation","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_kernel","type":"address"},{"name":"_appId","type":"bytes32"},{"name":"_initializePayload","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"ProxyDeposit","type":"event"}] -------------------------------------------------------------------------------- /interfaces/BeaconChainDepositor.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/ChangeNodeOperatorManagers.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_trustedCaller","type":"address"},{"internalType":"address","name":"_nodeOperatorsRegistry","type":"address"},{"internalType":"address","name":"_acl","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"acl","outputs":[{"internalType":"contract IACL","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"createEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"decodeEVMScriptCallData","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"internalType":"address","name":"oldManagerAddress","type":"address"},{"internalType":"address","name":"newManagerAddress","type":"address"}],"internalType":"struct ChangeNodeOperatorManagers.ChangeNodeOperatorManagersInput[]","name":"","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"nodeOperatorsRegistry","outputs":[{"internalType":"contract INodeOperatorsRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedCaller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/CurveRewardsManager.json: -------------------------------------------------------------------------------- 1 | [{"outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"name":"transfer_ownership","outputs":[],"inputs":[{"type":"address","name":"_to"}],"stateMutability":"nonpayable","type":"function"},{"name":"set_rewards_contract","outputs":[],"inputs":[{"type":"address","name":"_rewards_contract"}],"stateMutability":"nonpayable","type":"function"},{"name":"is_rewards_period_finished","outputs":[{"type":"bool","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"start_next_rewards_period","outputs":[],"inputs":[],"stateMutability":"nonpayable","type":"function"},{"name":"recover_erc20","outputs":[],"inputs":[{"type":"address","name":"_token"}],"stateMutability":"nonpayable","type":"function"},{"name":"recover_erc20","outputs":[],"inputs":[{"type":"address","name":"_token"},{"type":"address","name":"_recipient"}],"stateMutability":"nonpayable","type":"function"},{"name":"owner","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function"},{"name":"rewards_contract","outputs":[{"type":"address","name":""}],"inputs":[],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/DeactivateNodeOperators.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_trustedCaller","type":"address"},{"internalType":"address","name":"_nodeOperatorsRegistry","type":"address"},{"internalType":"address","name":"_acl","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"acl","outputs":[{"internalType":"contract IACL","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"createEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"decodeEVMScriptCallData","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"internalType":"address","name":"managerAddress","type":"address"}],"internalType":"struct DeactivateNodeOperators.DeactivateNodeOperatorInput[]","name":"","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"nodeOperatorsRegistry","outputs":[{"internalType":"contract INodeOperatorsRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedCaller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/EIP712StETH.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_stETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ZeroStETHAddress","type":"error"},{"inputs":[{"internalType":"address","name":"_stETH","type":"address"}],"name":"domainSeparatorV4","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_stETH","type":"address"}],"name":"eip712Domain","outputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_stETH","type":"address"},{"internalType":"bytes32","name":"_structHash","type":"bytes32"}],"name":"hashTypedDataV4","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/GateSealFactory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "GateSealCreated", 4 | "inputs": [{ "name": "gate_seal", "type": "address", "indexed": false }], 5 | "anonymous": false, 6 | "type": "event" 7 | }, 8 | { 9 | "stateMutability": "nonpayable", 10 | "type": "constructor", 11 | "inputs": [{ "name": "_blueprint", "type": "address" }], 12 | "outputs": [] 13 | }, 14 | { 15 | "stateMutability": "view", 16 | "type": "function", 17 | "name": "get_blueprint", 18 | "inputs": [], 19 | "outputs": [{ "name": "", "type": "address" }] 20 | }, 21 | { 22 | "stateMutability": "nonpayable", 23 | "type": "function", 24 | "name": "create_gate_seal", 25 | "inputs": [ 26 | { "name": "_sealing_committee", "type": "address" }, 27 | { "name": "_seal_duration_seconds", "type": "uint256" }, 28 | { "name": "_sealables", "type": "address[]" }, 29 | { "name": "_expiry_timestamp", "type": "uint256" } 30 | ], 31 | "outputs": [] 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /interfaces/IAccountingOracle.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"getConsensusContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IConsensusContract.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"getChainConfig","outputs":[{"internalType":"uint256","name":"slotsPerEpoch","type":"uint256"},{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentFrame","outputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"reportProcessingDeadlineSlot","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFrameConfig","outputs":[{"internalType":"uint256","name":"initialEpoch","type":"uint256"},{"internalType":"uint256","name":"epochsPerFrame","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getInitialRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getIsMember","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IDepositContract.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"get_deposit_root","outputs":[{"internalType":"bytes32","name":"rootHash","type":"bytes32"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IERC2612.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"},{"name":"value","type":"uint256"},{"name":"deadline","type":"uint256"},{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IETHRegistrarController.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"name","type":"string"},{"name":"duration","type":"uint256"}],"name":"rentPrice","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"name","type":"string"},{"name":"owner","type":"address"},{"name":"duration","type":"uint256"},{"name":"secret","type":"bytes32"}],"name":"register","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"MIN_REGISTRATION_DURATION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"minCommitmentAge","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"name","type":"string"}],"name":"valid","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"name","type":"string"}],"name":"available","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxCommitmentAge","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"commitment","type":"bytes32"}],"name":"commit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"name","type":"string"},{"name":"owner","type":"address"},{"name":"secret","type":"bytes32"}],"name":"makeCommitment","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"pure","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IEVMScriptFactory.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_creator", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "bytes", 11 | "name": "_evmScriptCallData", 12 | "type": "bytes" 13 | } 14 | ], 15 | "name": "createEVMScript", 16 | "outputs": [ 17 | { 18 | "internalType": "bytes", 19 | "name": "", 20 | "type": "bytes" 21 | } 22 | ], 23 | "stateMutability": "nonpayable", 24 | "type": "function" 25 | } 26 | ] -------------------------------------------------------------------------------- /interfaces/IHashConsensus.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[],"name":"getChainConfig","outputs":[{"name":"slotsPerEpoch","type":"uint256"},{"name":"secondsPerSlot","type":"uint256"},{"name":"genesisTime","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFrameConfig","outputs":[{"name":"initialEpoch","type":"uint256"},{"name":"epochsPerFrame","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentFrame","outputs":[{"name":"refSlot","type":"uint256"},{"name":"reportProcessingDeadlineSlot","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IInterfaceResolver.json: -------------------------------------------------------------------------------- 1 | [{"constant":true,"inputs":[{"name":"node","type":"bytes32"},{"name":"interfaceID","type":"bytes4"}],"name":"interfaceImplementer","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/ILegacyOracle.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"getBeaconSpec","outputs":[{"internalType":"uint64","name":"epochsPerFrame","type":"uint64"},{"internalType":"uint64","name":"slotsPerEpoch","type":"uint64"},{"internalType":"uint64","name":"secondsPerSlot","type":"uint64"},{"internalType":"uint64","name":"genesisTime","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastCompletedEpochId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"clBalance","type":"uint256"},{"internalType":"uint256","name":"clValidators","type":"uint256"}],"name":"handleConsensusLayerReport","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /interfaces/ILido.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"uint256","name":"_currentReportTimestamp","type":"uint256"},{"internalType":"uint256","name":"_timeElapsedSeconds","type":"uint256"},{"internalType":"uint256","name":"_clValidators","type":"uint256"},{"internalType":"uint256","name":"_clBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256[]","name":"_withdrawalFinalizationBatches","type":"uint256[]"},{"internalType":"uint256","name":"_simulatedShareRate","type":"uint256"}],"name":"handleOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /interfaces/ILidoExecutionLayerRewardsVault.json: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"_maxAmount","type":"uint256"}],"name":"withdrawRewards","outputs":[{"name":"amount","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /interfaces/INFTDescriptor.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"uint256","name":"_requestId","type":"uint256"}],"name":"constructTokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IOracleReportSanityChecker.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"uint256","name":"_exitRequestsCount","type":"uint256"}],"name":"checkExitBusOracleReport","outputs":[],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IPausable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "name": "isPaused", 5 | "outputs": [ 6 | { 7 | "internalType": "bool", 8 | "name": "", 9 | "type": "bool" 10 | } 11 | ], 12 | "stateMutability": "view", 13 | "type": "function" 14 | } 15 | ] -------------------------------------------------------------------------------- /interfaces/IPostTokenRebaseReceiver.json: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"_reportTimestamp","type":"uint256"},{"name":"_timeElapsed","type":"uint256"},{"name":"_preTotalShares","type":"uint256"},{"name":"_preTotalEther","type":"uint256"},{"name":"_postTotalShares","type":"uint256"},{"name":"_postTotalEther","type":"uint256"},{"name":"_sharesMintedAsFees","type":"uint256"}],"name":"handlePostTokenRebase","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IReportAsyncProcessor.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"discardConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"report","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /interfaces/ITokenRateUpdatable.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "uint256", 6 | "name": "tokenRate_", 7 | "type": "uint256" 8 | }, 9 | { 10 | "internalType": "uint256", 11 | "name": "rateUpdatedL1Timestamp_", 12 | "type": "uint256" 13 | } 14 | ], 15 | "name": "updateRate", 16 | "outputs": [], 17 | "stateMutability": "nonpayable", 18 | "type": "function" 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /interfaces/IWithdrawalQueue.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"uint256[]","name":"_requestIds","type":"uint256[]"}],"name":"getWithdrawalStatus","outputs":[{"components":[{"internalType":"uint256","name":"amountOfStETH","type":"uint256"},{"internalType":"uint256","name":"amountOfShares","type":"uint256"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"bool","name":"isFinalized","type":"bool"},{"internalType":"bool","name":"isClaimed","type":"bool"}],"internalType":"struct IWithdrawalQueue.WithdrawalRequestStatus[]","name":"statuses","type":"tuple[]"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IWithdrawalVault.json: -------------------------------------------------------------------------------- 1 | [{"constant":false,"inputs":[{"name":"_amount","type":"uint256"}],"name":"withdrawWithdrawals","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /interfaces/IncreaseNodeOperatorStakingLimit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_nodeOperatorsRegistry", 7 | "type": "address" 8 | } 9 | ], 10 | "stateMutability": "nonpayable", 11 | "type": "constructor" 12 | }, 13 | { 14 | "inputs": [ 15 | { 16 | "internalType": "address", 17 | "name": "_creator", 18 | "type": "address" 19 | }, 20 | { 21 | "internalType": "bytes", 22 | "name": "_evmScriptCallData", 23 | "type": "bytes" 24 | } 25 | ], 26 | "name": "createEVMScript", 27 | "outputs": [ 28 | { 29 | "internalType": "bytes", 30 | "name": "", 31 | "type": "bytes" 32 | } 33 | ], 34 | "stateMutability": "view", 35 | "type": "function" 36 | }, 37 | { 38 | "inputs": [ 39 | { 40 | "internalType": "bytes", 41 | "name": "_evmScriptCallData", 42 | "type": "bytes" 43 | } 44 | ], 45 | "name": "decodeEVMScriptCallData", 46 | "outputs": [ 47 | { 48 | "internalType": "uint256", 49 | "name": "_nodeOperatorId", 50 | "type": "uint256" 51 | }, 52 | { 53 | "internalType": "uint256", 54 | "name": "_stakingLimit", 55 | "type": "uint256" 56 | } 57 | ], 58 | "stateMutability": "pure", 59 | "type": "function" 60 | }, 61 | { 62 | "inputs": [], 63 | "name": "nodeOperatorsRegistry", 64 | "outputs": [ 65 | { 66 | "internalType": "contract INodeOperatorsRegistry", 67 | "name": "", 68 | "type": "address" 69 | } 70 | ], 71 | "stateMutability": "view", 72 | "type": "function" 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /interfaces/LidoExecutionLayerRewardsVault.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_lido","type":"address"},{"internalType":"address","name":"_treasury","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ETHReceived","type":"event"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxAmount","type":"uint256"}],"name":"withdrawRewards","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] -------------------------------------------------------------------------------- /interfaces/PositiveTokenRebaseLimiter.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"name":"NegativeTotalPooledEther","type":"error"},{"inputs":[],"name":"TooHighTokenRebaseLimit","type":"error"},{"inputs":[],"name":"TooLowTokenRebaseLimit","type":"error"},{"inputs":[],"name":"LIMITER_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNLIMITED_REBASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/RemoveRewardProgram.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_trustedCaller", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "address", 11 | "name": "_rewardProgramsRegistry", 12 | "type": "address" 13 | } 14 | ], 15 | "stateMutability": "nonpayable", 16 | "type": "constructor" 17 | }, 18 | { 19 | "inputs": [ 20 | { 21 | "internalType": "address", 22 | "name": "_creator", 23 | "type": "address" 24 | }, 25 | { 26 | "internalType": "bytes", 27 | "name": "_evmScriptCallData", 28 | "type": "bytes" 29 | } 30 | ], 31 | "name": "createEVMScript", 32 | "outputs": [ 33 | { 34 | "internalType": "bytes", 35 | "name": "", 36 | "type": "bytes" 37 | } 38 | ], 39 | "stateMutability": "view", 40 | "type": "function" 41 | }, 42 | { 43 | "inputs": [ 44 | { 45 | "internalType": "bytes", 46 | "name": "_evmScriptCallData", 47 | "type": "bytes" 48 | } 49 | ], 50 | "name": "decodeEVMScriptCallData", 51 | "outputs": [ 52 | { 53 | "internalType": "address", 54 | "name": "_rewardProgram", 55 | "type": "address" 56 | } 57 | ], 58 | "stateMutability": "pure", 59 | "type": "function" 60 | }, 61 | { 62 | "inputs": [], 63 | "name": "rewardProgramsRegistry", 64 | "outputs": [ 65 | { 66 | "internalType": "contract RewardProgramsRegistry", 67 | "name": "", 68 | "type": "address" 69 | } 70 | ], 71 | "stateMutability": "view", 72 | "type": "function" 73 | }, 74 | { 75 | "inputs": [], 76 | "name": "trustedCaller", 77 | "outputs": [ 78 | { 79 | "internalType": "address", 80 | "name": "", 81 | "type": "address" 82 | } 83 | ], 84 | "stateMutability": "view", 85 | "type": "function" 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /interfaces/SetNodeOperatorNames.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_trustedCaller","type":"address"},{"internalType":"address","name":"_nodeOperatorsRegistry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"createEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"decodeEVMScriptCallData","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"internalType":"string","name":"name","type":"string"}],"internalType":"struct SetNodeOperatorNames.SetNameInput[]","name":"","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"nodeOperatorsRegistry","outputs":[{"internalType":"contract INodeOperatorsRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedCaller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/SetNodeOperatorRewardAddresses.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_trustedCaller","type":"address"},{"internalType":"address","name":"_nodeOperatorsRegistry","type":"address"},{"internalType":"address","name":"_lido","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"createEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"decodeEVMScriptCallData","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"internalType":"address","name":"rewardAddress","type":"address"}],"internalType":"struct SetNodeOperatorRewardAddresses.SetRewardAddressInput[]","name":"","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"lido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nodeOperatorsRegistry","outputs":[{"internalType":"contract INodeOperatorsRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedCaller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/SetVettedValidatorsLimits.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_trustedCaller","type":"address"},{"internalType":"address","name":"_nodeOperatorsRegistry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"createEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"decodeEVMScriptCallData","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"stakingLimit","type":"uint256"}],"internalType":"struct SetVettedValidatorsLimits.VettedValidatorsLimitInput[]","name":"","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"nodeOperatorsRegistry","outputs":[{"internalType":"contract INodeOperatorsRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedCaller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/SigningKeys.json: -------------------------------------------------------------------------------- 1 | [{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"pubkey","type":"bytes"}],"name":"SigningKeyAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"nodeOperatorId","type":"uint256"},{"indexed":false,"name":"pubkey","type":"bytes"}],"name":"SigningKeyRemoved","type":"event"}] -------------------------------------------------------------------------------- /interfaces/UpdateTargetValidatorLimits.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_trustedCaller","type":"address"},{"internalType":"address","name":"_nodeOperatorsRegistry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"_creator","type":"address"},{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"createEVMScript","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_evmScriptCallData","type":"bytes"}],"name":"decodeEVMScriptCallData","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetLimit","type":"uint256"}],"internalType":"struct UpdateTargetValidatorLimits.TargetValidatorsLimit[]","name":"","type":"tuple[]"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"nodeOperatorsRegistry","outputs":[{"internalType":"contract INodeOperatorsRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedCaller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/Versioned.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] -------------------------------------------------------------------------------- /interfaces/WithdrawalContractProxy.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"proxy_changeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proxy_getAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxy_getIsOssified","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"setupCalldata","type":"bytes"}],"name":"proxy_upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] 2 | -------------------------------------------------------------------------------- /interfaces/WithdrawalVaultManager.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"proxy_changeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proxy_getAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxy_getIsOssified","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"setupCalldata","type":"bytes"}],"name":"proxy_upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] 2 | -------------------------------------------------------------------------------- /interfaces/obol_split/ObolLidoSplit.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_feeRecipient","type":"address"},{"internalType":"uint256","name":"_feeShare","type":"uint256"},{"internalType":"contract ERC20","name":"_stETH","type":"address"},{"internalType":"contract ERC20","name":"_wstETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"Invalid_Address","type":"error"},{"inputs":[],"name":"Invalid_FeeRecipient","type":"error"},{"inputs":[{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"Invalid_FeeShare","type":"error"},{"inputs":[],"name":"distribute","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeShare","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"rescueFunds","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"splitWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"stETH","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wstETH","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /interfaces/obol_split/ObolLidoSplitFactory.json: -------------------------------------------------------------------------------- 1 | [{"inputs":[{"internalType":"address","name":"_feeRecipient","type":"address"},{"internalType":"uint256","name":"_feeShare","type":"uint256"},{"internalType":"contract ERC20","name":"_stETH","type":"address"},{"internalType":"contract ERC20","name":"_wstETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"Invalid_Wallet","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"split","type":"address"}],"name":"CreateObolLidoSplit","type":"event"},{"inputs":[{"internalType":"address","name":"splitWallet","type":"address"}],"name":"createSplit","outputs":[{"internalType":"address","name":"lidoSplit","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lidoSplitImpl","outputs":[{"internalType":"contract ObolLidoSplit","name":"","type":"address"}],"stateMutability":"view","type":"function"}] 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "see also pyproject.toml", 3 | "name": "scripts", 4 | "description": "Repository for Lido DAO Aragon voting scripts.", 5 | "authors": "Lido devteam", 6 | "license": "MIT", 7 | "readme": "README.md", 8 | "homepage": "https://mainnet.lido.fi", 9 | "dependencies": { 10 | "@lido-js/ganache": "=7.9.2-lido", 11 | "hardhat": "^2.22.19" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "lido-dao-scripts" 3 | version = "0.1.0" 4 | description = "Repository for Lido DAO Aragon voting scripts." 5 | authors = ["Lido devteam"] 6 | license = "MIT" 7 | readme = "README.md" 8 | homepage = "https://mainnet.lido.fi" 9 | package-mode = false 10 | 11 | [tool.poetry.dependencies] 12 | python = ">=3.10,<3.11" 13 | eth-brownie = "1.20.2" 14 | avotes-parser-core = "~0.5.6" 15 | hexbytes = "0.3.1" 16 | ipfs-cid = "^1.0.0" 17 | 18 | [tool.poetry.dev-dependencies] 19 | black = "~24.2.0" 20 | pre-commit = "~2.19.0" 21 | pytest-check = "2.2.2" 22 | 23 | [build-system] 24 | requires = ["poetry-core>=1.0.0"] 25 | build-backend = "poetry.core.masonry.api" 26 | 27 | [tool.black] 28 | line-length = 120 29 | target-version = ['py310'] 30 | include = '\.pyi?$' 31 | 32 | [tool.pytest.ini_options] 33 | testpaths = [ 34 | "tests", 35 | ] 36 | filterwarnings = [ 37 | "ignore:abi.(decode|encode):DeprecationWarning", 38 | "ignore:rpc.(snapshot|revert):FutureWarning", 39 | ] 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/tests/__init__.py -------------------------------------------------------------------------------- /tests/acceptance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/tests/acceptance/__init__.py -------------------------------------------------------------------------------- /tests/acceptance/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from utils.config import contracts 5 | from utils.import_current_votes import is_there_any_vote_scripts, is_there_any_upgrade_scripts, start_and_execute_votes 6 | 7 | from utils.test.helpers import ETH 8 | from utils.test.oracle_report_helpers import oracle_report 9 | from utils.test.simple_dvt_helpers import fill_simple_dvt_ops_vetted_keys 10 | 11 | ENV_REPORT_AFTER_VOTE = "REPORT_AFTER_VOTE" 12 | ENV_FILL_SIMPLE_DVT = "FILL_SIMPLE_DVT" 13 | 14 | 15 | @pytest.fixture(scope="module", autouse=is_there_any_vote_scripts() or is_there_any_upgrade_scripts()) 16 | def autoexecute_vote(helpers, vote_ids_from_env, accounts, stranger, module_isolation): 17 | if vote_ids_from_env: 18 | helpers.execute_votes(accounts, vote_ids_from_env, contracts.voting) 19 | else: 20 | start_and_execute_votes(contracts.voting, helpers) 21 | if os.getenv(ENV_FILL_SIMPLE_DVT): 22 | print(f"Prefilling SimpleDVT...") 23 | fill_simple_dvt_ops_vetted_keys(stranger) 24 | 25 | if os.getenv(ENV_REPORT_AFTER_VOTE): 26 | oracle_report(cl_diff=ETH(523), exclude_vaults_balances=False) 27 | -------------------------------------------------------------------------------- /tests/acceptance/test_burner.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface # type: ignore 3 | 4 | from utils.config import ( 5 | contracts, 6 | BURNER, 7 | TOTAL_NON_COVER_SHARES_BURNT, 8 | TOTAL_COVER_SHARES_BURNT, 9 | ) 10 | 11 | 12 | @pytest.fixture(scope="module") 13 | def contract() -> interface.Burner: 14 | return interface.Burner(BURNER) 15 | 16 | 17 | def test_links(contract): 18 | assert contract.STETH() == contracts.lido 19 | assert contract.TREASURY() == contracts.agent 20 | -------------------------------------------------------------------------------- /tests/acceptance/test_el_rewards_vault.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface # type: ignore 3 | 4 | from utils.config import contracts, EXECUTION_LAYER_REWARDS_VAULT 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def contract() -> interface.WithdrawalVault: 9 | return interface.WithdrawalVault(EXECUTION_LAYER_REWARDS_VAULT) 10 | 11 | 12 | def test_vault(contract): 13 | assert contract.LIDO() == contracts.lido 14 | assert contract.TREASURY() == contracts.agent 15 | -------------------------------------------------------------------------------- /tests/acceptance/test_gate_seal_acceptance.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract, interface, web3 # type: ignore 3 | from brownie.network.account import Account 4 | 5 | from utils.config import GATE_SEAL_EXPIRY_TIMESTAMP, GATE_SEAL_PAUSE_DURATION, contracts, GATE_SEAL, GATE_SEAL_COMMITTEE 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def gate_seal_committee(accounts) -> Account: 10 | return accounts.at(GATE_SEAL_COMMITTEE, force=True) 11 | 12 | 13 | @pytest.fixture(scope="module") 14 | def contract() -> Contract: 15 | return interface.GateSeal(GATE_SEAL) 16 | 17 | 18 | def test_gate_seal(contract: Contract, gate_seal_committee: Account): 19 | assert contract.get_sealing_committee() == gate_seal_committee 20 | 21 | sealables = contract.get_sealables() 22 | assert len(sealables) == 2 23 | assert contracts.validators_exit_bus_oracle.address in sealables 24 | assert contracts.withdrawal_queue.address in sealables 25 | 26 | _check_role(contracts.validators_exit_bus_oracle, "PAUSE_ROLE", contract.address) 27 | _check_role(contracts.withdrawal_queue, "PAUSE_ROLE", contract.address) 28 | 29 | assert contract.get_seal_duration_seconds() == GATE_SEAL_PAUSE_DURATION 30 | assert contract.get_expiry_timestamp() == GATE_SEAL_EXPIRY_TIMESTAMP 31 | assert not contract.is_expired() 32 | 33 | 34 | def _check_role(contract: Contract, role: str, holder: str): 35 | role_bytes = web3.keccak(text=role).hex() 36 | assert contract.getRoleMemberCount(role_bytes) == 1, f"Role {role} on {contract} should have exactly one holder" 37 | assert contract.getRoleMember(role_bytes, 0) == holder, f"Role {role} holder on {contract} should be {holder}" 38 | -------------------------------------------------------------------------------- /tests/acceptance/test_oracle_daemon_config.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface # type: ignore 3 | 4 | from utils.config import ( 5 | ORACLE_DAEMON_CONFIG, 6 | NORMALIZED_CL_REWARD_PER_EPOCH, 7 | NORMALIZED_CL_REWARD_MISTAKE_RATE_BP, 8 | REBASE_CHECK_NEAREST_EPOCH_DISTANCE, 9 | REBASE_CHECK_DISTANT_EPOCH_DISTANCE, 10 | VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS, 11 | VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS, 12 | PREDICTION_DURATION_IN_SLOTS, 13 | FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT, 14 | NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP, 15 | ) 16 | 17 | 18 | mainnet_config: dict[str, int] = { 19 | "NORMALIZED_CL_REWARD_PER_EPOCH": NORMALIZED_CL_REWARD_PER_EPOCH, 20 | "NORMALIZED_CL_REWARD_MISTAKE_RATE_BP": NORMALIZED_CL_REWARD_MISTAKE_RATE_BP, 21 | "REBASE_CHECK_NEAREST_EPOCH_DISTANCE": REBASE_CHECK_NEAREST_EPOCH_DISTANCE, 22 | "REBASE_CHECK_DISTANT_EPOCH_DISTANCE": REBASE_CHECK_DISTANT_EPOCH_DISTANCE, 23 | "VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS": VALIDATOR_DELAYED_TIMEOUT_IN_SLOTS, 24 | "VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS": VALIDATOR_DELINQUENT_TIMEOUT_IN_SLOTS, 25 | "PREDICTION_DURATION_IN_SLOTS": PREDICTION_DURATION_IN_SLOTS, 26 | "FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT": FINALIZATION_MAX_NEGATIVE_REBASE_EPOCH_SHIFT, 27 | "NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP": NODE_OPERATOR_NETWORK_PENETRATION_THRESHOLD_BP, 28 | } 29 | 30 | 31 | @pytest.fixture(scope="module") 32 | def contract() -> interface.OracleDaemonConfig: 33 | return interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) 34 | 35 | 36 | def test_oracle_daemon_config(contract): 37 | def values_to_int(values) -> list[int]: 38 | return list(map(lambda x: int(str(x), 16), values)) 39 | 40 | contract_values = contract.getList(list(mainnet_config.keys())) 41 | contract_config = dict(zip(mainnet_config.keys(), values_to_int(contract_values))) 42 | 43 | assert mainnet_config == contract_config, "OracleDaemonConfig values are incorrect" 44 | -------------------------------------------------------------------------------- /tests/acceptance/test_withdrawal_queue.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface, ZERO_ADDRESS, reverts # type: ignore 3 | 4 | from utils.config import ( 5 | contracts, 6 | WITHDRAWAL_QUEUE, 7 | WITHDRAWAL_QUEUE_IMPL, 8 | WQ_ERC721_TOKEN_NAME, 9 | WQ_ERC721_TOKEN_SYMBOL, 10 | WQ_ERC721_TOKEN_BASE_URI, 11 | ) 12 | from utils.evm_script import encode_error 13 | 14 | @pytest.fixture(scope="module") 15 | def contract() -> interface.WithdrawalQueueERC721: 16 | return interface.WithdrawalQueueERC721(WITHDRAWAL_QUEUE) 17 | 18 | 19 | def test_proxy(contract): 20 | proxy = interface.OssifiableProxy(contract) 21 | assert proxy.proxy__getImplementation() == WITHDRAWAL_QUEUE_IMPL 22 | assert proxy.proxy__getAdmin() == contracts.agent.address 23 | 24 | 25 | def test_versioned(contract): 26 | assert contract.getContractVersion() == 1 27 | 28 | 29 | def test_initialize(contract): 30 | with reverts(encode_error("NonZeroContractVersionOnInit()")): 31 | contract.initialize(contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), {"from": contracts.voting}) 32 | 33 | 34 | def test_petrified(contract): 35 | impl = interface.WithdrawalQueueERC721(WITHDRAWAL_QUEUE_IMPL) 36 | with reverts(encode_error("NonZeroContractVersionOnInit()")): 37 | impl.initialize(contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), {"from": contracts.voting}) 38 | 39 | 40 | def test_pausable_until(contract): 41 | assert contract.isPaused() == False 42 | assert contract.getResumeSinceTimestamp() > 0 43 | 44 | 45 | def test_withdrawal_queue(contract): 46 | assert contract.WSTETH() == contracts.wsteth 47 | assert contract.STETH() == contracts.lido 48 | assert contract.bunkerModeSinceTimestamp() == contract.BUNKER_MODE_DISABLED_TIMESTAMP() 49 | 50 | 51 | def test_withdrawal_queue_erc721(contract): 52 | assert contract.name() == WQ_ERC721_TOKEN_NAME 53 | assert contract.symbol() == WQ_ERC721_TOKEN_SYMBOL 54 | assert contract.getBaseURI() == WQ_ERC721_TOKEN_BASE_URI 55 | assert contract.getNFTDescriptorAddress() == ZERO_ADDRESS 56 | -------------------------------------------------------------------------------- /tests/acceptance/test_withdrawal_vault.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import interface, reverts # type: ignore 3 | 4 | from utils.config import contracts, WITHDRAWAL_VAULT, WITHDRAWAL_VAULT_IMPL 5 | from utils.evm_script import encode_error 6 | 7 | 8 | @pytest.fixture(scope="module") 9 | def contract() -> interface.WithdrawalVault: 10 | return interface.WithdrawalVault(WITHDRAWAL_VAULT) 11 | 12 | 13 | def test_proxy(contract): 14 | proxy = interface.WithdrawalVaultManager(contract) 15 | assert proxy.implementation() == WITHDRAWAL_VAULT_IMPL 16 | assert proxy.proxy_getAdmin() == contracts.voting.address 17 | 18 | 19 | def test_versioned(contract): 20 | assert contract.getContractVersion() == 1 21 | 22 | 23 | def test_initialize(contract): 24 | with reverts(encode_error("NonZeroContractVersionOnInit()")): 25 | contract.initialize({"from": contracts.voting}) 26 | 27 | 28 | def test_petrified(): 29 | impl = interface.WithdrawalVault(WITHDRAWAL_VAULT_IMPL) 30 | with reverts(encode_error("NonZeroContractVersionOnInit()")): 31 | impl.initialize({"from": contracts.voting}) 32 | 33 | 34 | def test_withdrawals_vault(contract): 35 | assert contract.LIDO() == contracts.lido 36 | assert contract.TREASURY() == contracts.agent 37 | assert contract.LIDO() == contracts.lido_locator.lido() 38 | assert contract.TREASURY() == contracts.lido_locator.treasury() 39 | -------------------------------------------------------------------------------- /tests/internal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/tests/internal/__init__.py -------------------------------------------------------------------------------- /tests/internal/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | # This is help to total ignore this test at CI 5 | # They are disrepair, not even marked as skipped. 6 | # Works with `brownie test` and `brownie test tests/internal` 7 | # Doesn't work for direct run `brownie test tests/internal/test_ipfs.py` 8 | def pytest_ignore_collect(path, config): 9 | return not os.getenv("WITH_INTERNAL_TESTS") 10 | -------------------------------------------------------------------------------- /tests/regression/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/tests/regression/__init__.py -------------------------------------------------------------------------------- /tests/regression/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from utils.config import contracts 5 | from utils.import_current_votes import is_there_any_vote_scripts, is_there_any_upgrade_scripts, start_and_execute_votes 6 | from utils.test.extra_data import ExtraDataService 7 | 8 | from utils.test.helpers import ETH 9 | from utils.test.oracle_report_helpers import oracle_report 10 | from utils.test.simple_dvt_helpers import fill_simple_dvt_ops_vetted_keys 11 | 12 | ENV_REPORT_AFTER_VOTE = "REPORT_AFTER_VOTE" 13 | ENV_FILL_SIMPLE_DVT = "FILL_SIMPLE_DVT" 14 | 15 | 16 | @pytest.fixture(scope="module", autouse=is_there_any_vote_scripts() or is_there_any_upgrade_scripts()) 17 | def autoexecute_vote(request, helpers, vote_ids_from_env, accounts, stranger, module_isolation): 18 | if vote_ids_from_env: 19 | helpers.execute_votes(accounts, vote_ids_from_env, contracts.voting) 20 | else: 21 | start_and_execute_votes(contracts.voting, helpers) 22 | 23 | if os.getenv(ENV_FILL_SIMPLE_DVT): 24 | print(f"Prefilling SimpleDVT...") 25 | fill_simple_dvt_ops_vetted_keys(stranger) 26 | 27 | if os.getenv(ENV_REPORT_AFTER_VOTE): 28 | oracle_report(cl_diff=ETH(523), exclude_vaults_balances=False) 29 | 30 | @pytest.fixture() 31 | def extra_data_service() -> ExtraDataService: 32 | return ExtraDataService() 33 | -------------------------------------------------------------------------------- /tests/regression/test_burn_shares.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for lido burnShares method 3 | """ 4 | import pytest 5 | from utils.config import contracts 6 | from brownie import reverts, ZERO_ADDRESS, accounts, chain 7 | from utils.evm_script import encode_error 8 | 9 | def test_burn_shares_by_stranger(stranger): 10 | lido = accounts.at(contracts.lido, force=True) 11 | 12 | # Stake ETH by stranger to receive stETH 13 | stranger_submit_amount = 10**18 14 | contracts.lido.submit(ZERO_ADDRESS, {"from": stranger, "amount": stranger_submit_amount}) 15 | stranger_steth_balance_before = contracts.lido.balanceOf(stranger) 16 | shares_to_burn = contracts.lido.sharesOf(stranger) 17 | assert abs(stranger_submit_amount - stranger_steth_balance_before) <= 2 18 | 19 | total_eth = contracts.lido.totalSupply() 20 | total_shares = contracts.lido.getTotalShares() 21 | 22 | # Test that stranger can't burnShares 23 | with reverts(encode_error("AppAuthLidoFailed()")): 24 | contracts.burner.commitSharesToBurn(shares_to_burn, {"from": stranger}) 25 | 26 | contracts.lido.approve(contracts.burner, 10**24, {"from": stranger}) 27 | 28 | contracts.burner.requestBurnSharesForCover(stranger, shares_to_burn, {"from": lido}) 29 | 30 | prev_report = contracts.lido.getBeaconStat().dict() 31 | beacon_validators = prev_report["beaconValidators"] 32 | beacon_balance = prev_report["beaconBalance"] 33 | contracts.lido.handleOracleReport( 34 | chain.time(), 35 | 0, 36 | beacon_validators, 37 | beacon_balance, 38 | 0, 39 | 0, 40 | shares_to_burn, 41 | [], 42 | 0, 43 | {"from": contracts.accounting_oracle}, 44 | ) 45 | 46 | assert contracts.lido.sharesOf(stranger) == 0 47 | assert contracts.lido.totalSupply() == total_eth 48 | assert contracts.lido.getTotalShares() == total_shares - shares_to_burn 49 | -------------------------------------------------------------------------------- /tests/regression/test_nft_url.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from utils.test.helpers import ETH 3 | from utils.config import contracts 4 | from utils.test.oracle_report_helpers import oracle_report 5 | 6 | 7 | def test_nft_url(steth_holder): 8 | contracts.lido.approve(contracts.withdrawal_queue.address, ETH(1), {"from": steth_holder}) 9 | contracts.withdrawal_queue.requestWithdrawals([ETH(1)], steth_holder, {"from": steth_holder}) 10 | 11 | id = contracts.withdrawal_queue.getWithdrawalRequests(steth_holder, {"from": steth_holder})[0] 12 | 13 | token_uri = contracts.withdrawal_queue.tokenURI(id) 14 | 15 | r = requests.get(token_uri) 16 | 17 | assert r.status_code == 200 18 | body = r.json() 19 | assert "name" in body 20 | assert "description" in body 21 | assert "image" in body 22 | 23 | """ report """ 24 | report_tx = None 25 | # first oracle report, requests might not get finalized due to sanity check requestTimestampMargin depending on current fork time 26 | report_tx = oracle_report()[0] 27 | # second report requests will get finalized for sure 28 | if not report_tx.events.count("WithdrawalsFinalized") == 1: 29 | report_tx = oracle_report()[0] 30 | 31 | token_uri = contracts.withdrawal_queue.tokenURI(id) 32 | 33 | r = requests.get(token_uri) 34 | 35 | assert r.status_code == 200 36 | body = r.json() 37 | assert "name" in body 38 | assert "description" in body 39 | assert "image" in body 40 | -------------------------------------------------------------------------------- /tests/snapshot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/tests/snapshot/__init__.py -------------------------------------------------------------------------------- /tests/snapshot/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from utils.import_current_votes import is_there_any_upgrade_scripts 4 | 5 | @pytest.fixture(scope="function", autouse=True) 6 | def skip_if_there_no_upgrade_scripts(): 7 | if not is_there_any_upgrade_scripts(): 8 | pytest.skip("No upgrade scripts detected") 9 | -------------------------------------------------------------------------------- /usecase/node_operators_management.md: -------------------------------------------------------------------------------- 1 | ### 🕸️ Notes for the node operators management ops 2 | 3 | #### Adding node operators 4 | 5 | Script to pack up adding new node operators in one vote 6 | 7 | ```bash 8 | NODE_OPERATORS_JSON=node_operators.json brownie run add_node_operators --network {name} 9 | ``` 10 | 11 | ##### node_operators.json 12 | 13 | ```json 14 | { 15 | "node_operators": [ 16 | { 17 | "name": "Test", 18 | "address": "0x000..." 19 | }, 20 | ... 21 | ] 22 | } 23 | 24 | ``` 25 | 26 | #### Setting node operators limits 27 | 28 | Script to pack up setting node operators staking limits in one vote 29 | 30 | ```bash 31 | NODE_OPERATORS_JSON=node_operators.json brownie run set_node_operators_limit --network {name} 32 | ``` 33 | 34 | ##### node_operators.json 35 | 36 | ```json 37 | { 38 | "node_operators": [ 39 | { 40 | "id": 1, 41 | "limit": 20 42 | }, 43 | ... 44 | ] 45 | } 46 | 47 | ``` 48 | -------------------------------------------------------------------------------- /usecase/reward_manager_tokens_recovery.md: -------------------------------------------------------------------------------- 1 | ### 💸 Rewards Manager Tokens Recoverer 2 | 3 | This repo contains contract RewardsManagerTokensRecoverer to simplify tokens recovering from Lido's reward managers via Aragon voting. 4 | 5 | #### Setup 6 | 7 | Please, bring back archived scripts and test first: 8 | ```shell 9 | cp ./tests/archive/xtest_rewards_manager_tokens_recoverer.py ./tests/test_rewards_manager_tokens_recoverer.py 10 | cp ./scripts/archive/deploy_rewards_manager_tokens_recoverer.py ./scripts 11 | ``` 12 | 13 | #### Deployment 14 | 15 | To run deployment of the RewardsManagerTokensRecoverer contract use the command 16 | `DEPLOYER= brownie run deploy_rewards_manager_tokens_recoverer`. 17 | 18 | #### Tests 19 | 20 | To run tests for the RewardsManagerTokensRecoverer contract use the command 21 | `brownie test ./tests/test_rewards_manager_tokens_recoverer.py -s`. 22 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lidofinance/scripts/68079f0d6488cb7f5811fdb76e8fcd29a0c498f0/utils/__init__.py -------------------------------------------------------------------------------- /utils/agent.py: -------------------------------------------------------------------------------- 1 | from utils.config import network_name, contracts, AGENT 2 | from utils.evm_script import ( 3 | encode_call_script, 4 | ) 5 | from typing import ( 6 | Tuple, 7 | Optional, 8 | Sequence, 9 | ) 10 | 11 | 12 | def agent_forward(call_script: Sequence[Tuple[str, str]]) -> Tuple[str, str]: 13 | agent = contracts.agent 14 | return (AGENT, agent.forward.encode_input(encode_call_script(call_script))) 15 | 16 | 17 | def dual_governance_agent_forward( 18 | call_script: Sequence[Tuple[str, str]], 19 | description: Optional[str] = "", 20 | ) -> Tuple[str, str]: 21 | 22 | if network_name() not in ["hoodi", "hoodi-fork"]: 23 | raise ValueError("Invalid network name") 24 | 25 | ## TODO: move up the scope when mainnet configuration for DG is updated 26 | from utils.config import DUAL_GOVERNANCE 27 | 28 | dual_governance = contracts.dual_governance 29 | (agent_address, agent_calldata) = agent_forward(call_script) 30 | return ( 31 | DUAL_GOVERNANCE, 32 | dual_governance.submitProposal.encode_input([(agent_address, 0, agent_calldata)], description), 33 | ) 34 | 35 | 36 | def agent_execute(target: str, value: str, data: str) -> Tuple[str, str]: 37 | agent = contracts.agent 38 | return (AGENT, agent.execute.encode_input(target, value, data)) 39 | -------------------------------------------------------------------------------- /utils/allowed_recipients_registry.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from brownie import interface 3 | 4 | from utils.config import contracts 5 | from .easy_track import create_permissions 6 | 7 | 8 | def set_limit_parameters(registry_address: str, limit: int, period_duration_months: int) -> Tuple[str, str]: 9 | registry = interface.AllowedRecipientRegistry(registry_address) 10 | return (registry.address, registry.setLimitParameters.encode_input(limit, period_duration_months)) 11 | 12 | 13 | def update_spent_amount(registry_address: str, spent_amount: int) -> Tuple[str, str]: 14 | registry = interface.AllowedRecipientRegistry(registry_address) 15 | return (registry.address, registry.updateSpentAmount.encode_input(spent_amount)) 16 | 17 | def unsafe_set_spent_amount(registry_address: str, spent_amount: int) -> Tuple[str, str]: 18 | registry = interface.AllowedRecipientRegistry(registry_address) 19 | return (registry.address, registry.unsafeSetSpentAmount.encode_input(spent_amount)) 20 | 21 | def create_top_up_allowed_recipient_permission(registry_address: str) -> str: 22 | return ( 23 | create_permissions(contracts.finance, "newImmediatePayment") 24 | + create_permissions(interface.AllowedRecipientRegistry(registry_address), "updateSpentAmount")[2:] 25 | ) 26 | -------------------------------------------------------------------------------- /utils/balance.py: -------------------------------------------------------------------------------- 1 | from brownie import accounts, web3 2 | from utils.test.helpers import ETH 3 | 4 | 5 | def set_balance_in_wei(address, balance): 6 | account = accounts.at(address, force=True) 7 | providers = ["evm_setAccountBalance", "hardhat_setBalance", "anvil_setBalance"] 8 | 9 | for provider in providers: 10 | if account.balance() == balance: 11 | break 12 | 13 | try: 14 | web3.provider.make_request(provider, [address, hex(balance)]) 15 | except ValueError as e: 16 | if e.args[0].get("message") != f"Method {provider} is not supported": 17 | raise e 18 | 19 | assert account.balance() == balance, f"Failed to set balance {balance} for account: {address}" 20 | return account 21 | 22 | 23 | def set_balance(address, balanceInEth): 24 | balance = ETH(balanceInEth) 25 | 26 | return set_balance_in_wei(address, balance) 27 | -------------------------------------------------------------------------------- /utils/brownie_prelude.py: -------------------------------------------------------------------------------- 1 | try: 2 | from brownie import interface 3 | except ImportError: 4 | print( 5 | 'You\'re probably running inside Brownie console. ' 6 | 'Please call:\n' 7 | 'set_console_globals(interface=interface)' 8 | ) 9 | 10 | 11 | def set_console_globals(**kwargs): 12 | """Extract interface from brownie environment.""" 13 | global interface 14 | interface = kwargs['interface'] 15 | -------------------------------------------------------------------------------- /utils/checksummed_address.py: -------------------------------------------------------------------------------- 1 | from eth_utils import keccak, to_bytes, ValidationError 2 | 3 | # https://eips.ethereum.org/EIPS/eip-55 4 | def checksum_encode(addr): # Takes a 20-byte binary address as input 5 | hex_addr = addr.hex() 6 | checksummed_buffer = "" 7 | 8 | # Treat the hex address as ascii/utf-8 for keccak256 hashing 9 | hashed_address = keccak(text=hex_addr).hex() 10 | 11 | # Iterate over each character in the hex address 12 | for nibble_index, character in enumerate(hex_addr): 13 | 14 | if character in "0123456789": 15 | # We can't upper-case the decimal digits 16 | checksummed_buffer += character 17 | elif character in "abcdef": 18 | # Check if the corresponding hex digit (nibble) in the hash is 8 or higher 19 | hashed_address_nibble = int(hashed_address[nibble_index], 16) 20 | if hashed_address_nibble > 7: 21 | checksummed_buffer += character.upper() 22 | else: 23 | checksummed_buffer += character 24 | else: 25 | raise ValidationError(f"Unrecognized hex character {character!r} at position {nibble_index}") 26 | 27 | return "0x" + checksummed_buffer 28 | 29 | 30 | def checksum_verify(address: str) -> bool: 31 | addr_bytes = to_bytes(hexstr=address) 32 | checksum_encoded = "" 33 | try: 34 | checksum_encoded = checksum_encode(addr_bytes) 35 | except: 36 | return False 37 | return checksum_encoded == address 38 | -------------------------------------------------------------------------------- /utils/csm.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from brownie import interface 3 | 4 | from utils.config import contracts 5 | 6 | def activate_public_release(csm_address: str) -> Tuple[str, str]: 7 | csm = interface.CSModule(csm_address) 8 | return (csm.address, csm.activatePublicRelease.encode_input()) 9 | -------------------------------------------------------------------------------- /utils/easy_track.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from utils.config import contracts 3 | 4 | from brownie import Contract 5 | 6 | 7 | def add_evmscript_factory(factory: Contract | str, permissions: str) -> Tuple[str, str]: 8 | easy_track = contracts.easy_track 9 | 10 | return (easy_track.address, easy_track.addEVMScriptFactory.encode_input(factory, permissions)) 11 | 12 | 13 | def remove_evmscript_factory(factory: Contract | str) -> Tuple[str, str]: 14 | easy_track = contracts.easy_track 15 | 16 | return (easy_track.address, easy_track.removeEVMScriptFactory.encode_input(factory)) 17 | 18 | 19 | def create_permissions_for_overloaded_method( 20 | contract: Contract, method: str, paramethers: Tuple[str, ...] = None 21 | ) -> str: 22 | method_description = getattr(contract, method) 23 | 24 | if len(method_description.methods) > 0: 25 | return contract.address + method_description.methods[paramethers].signature[2:] 26 | 27 | return create_permissions(contract, method) 28 | 29 | 30 | def create_permissions(contract: Contract, method: str) -> str: 31 | return contract.address + getattr(contract, method).signature[2:] 32 | 33 | 34 | def create_permissions_overloaded(contract: Contract, method: str, func_args: str) -> str: 35 | return contract.address + getattr(contract, method)[func_args].signature[2:] 36 | 37 | 38 | def set_motions_count_limit(motionsCountLimit: int) -> Tuple[str, str]: 39 | easy_track = contracts.easy_track 40 | 41 | return (easy_track.address, easy_track.setMotionsCountLimit.encode_input(motionsCountLimit)) 42 | -------------------------------------------------------------------------------- /utils/kernel.py: -------------------------------------------------------------------------------- 1 | from utils.config import contracts 2 | 3 | 4 | def update_app_implementation(app_id, new_implementation): 5 | kernel = contracts.kernel 6 | 7 | return ( 8 | kernel.address, 9 | kernel.setApp.encode_input( 10 | kernel.APP_BASES_NAMESPACE(), 11 | app_id, 12 | new_implementation 13 | ) 14 | ) 15 | -------------------------------------------------------------------------------- /utils/mainnet_fork.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from brownie import chain, accounts, interface 3 | 4 | from utils.config import VOTING, LDO_VOTE_EXECUTORS_FOR_TESTS, get_vote_duration 5 | 6 | 7 | @contextmanager 8 | def chain_snapshot(): 9 | try: 10 | print("Making chain snapshot...") 11 | chain.snapshot() 12 | yield 13 | finally: 14 | print("Reverting the chain...") 15 | chain.revert() 16 | 17 | 18 | def pass_and_exec_dao_vote(vote_id): 19 | dao_voting = interface.Voting(VOTING) 20 | 21 | if dao_voting.getVote(vote_id)["executed"]: 22 | print(f"[ok] Vote {vote_id} already executed") 23 | return 24 | 25 | helper_acct = accounts[0] 26 | 27 | if not dao_voting.canExecute(vote_id): 28 | print(f"Passing vote {vote_id}") 29 | 30 | for holder_addr in LDO_VOTE_EXECUTORS_FOR_TESTS: 31 | print(f" voting from {holder_addr}") 32 | helper_acct.transfer(holder_addr, "1 ether", silent=True) 33 | account = accounts.at(holder_addr, force=True) 34 | dao_voting.vote(vote_id, True, False, {"from": account, "silent": True}) 35 | 36 | # wait for the vote to end 37 | time_to_end = dao_voting.getVote(vote_id)["startDate"] + get_vote_duration() - chain.time() 38 | if time_to_end > 0: 39 | chain.sleep(time_to_end) 40 | 41 | chain.mine() 42 | 43 | assert dao_voting.canExecute(vote_id) 44 | 45 | print(f"Executing vote {vote_id}") 46 | 47 | dao_voting.executeVote(vote_id, {"from": helper_acct, "silent": True}) 48 | assert dao_voting.getVote(vote_id)["executed"] 49 | 50 | print(f"[ok] Vote {vote_id} executed") 51 | -------------------------------------------------------------------------------- /utils/node_operators.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from utils.evm_script import encode_call_script 3 | 4 | from utils.config import contracts 5 | 6 | 7 | def encode_set_node_operator_staking_limit(id, limit, registry): 8 | return ( 9 | registry.address, 10 | registry.setNodeOperatorStakingLimit.encode_input(id, limit) 11 | ) 12 | 13 | 14 | def encode_set_node_operators_staking_limits_evm_script(node_operators, registry): 15 | return encode_call_script([ 16 | encode_set_node_operator_staking_limit(id=node_operator["id"], 17 | limit=node_operator["limit"], 18 | registry=registry) 19 | for node_operator in node_operators 20 | ]) 21 | 22 | 23 | def encode_set_node_operator_name(id, name, registry): 24 | return ( 25 | registry.address, 26 | registry.setNodeOperatorName.encode_input(id, name) 27 | ) 28 | 29 | 30 | def encode_set_node_operator_reward_address(id, rewardAddress, registry): 31 | return ( 32 | registry.address, 33 | registry.setNodeOperatorRewardAddress.encode_input(id, rewardAddress) 34 | ) 35 | 36 | 37 | def get_node_operators(registry): 38 | return [{**registry.getNodeOperator(i, True), **{'index': i}} for i in range(registry.getNodeOperatorsCount())] 39 | 40 | 41 | def _encode_add_operator(address, name, registry): 42 | return registry.address, registry.addNodeOperator.encode_input(name, address) 43 | 44 | 45 | def encode_add_operator_lido(address, name): 46 | return _encode_add_operator(address, name, contracts.node_operators_registry) 47 | 48 | 49 | def deactivate_node_operator(id: int) -> Tuple[str, str]: 50 | curated_sm = contracts.node_operators_registry 51 | return (curated_sm.address, curated_sm.deactivateNodeOperator.encode_input(id)) 52 | -------------------------------------------------------------------------------- /utils/oracle.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from brownie import interface 3 | 4 | from utils.config import contracts 5 | 6 | 7 | def add_accounting_oracle_member(member: str, quorum: int) -> Tuple[str, str]: 8 | hash_consensus: interface.LidoOracle = contracts.hash_consensus_for_accounting_oracle 9 | 10 | return (hash_consensus.address, hash_consensus.addMember.encode_input(member, quorum)) 11 | 12 | 13 | def remove_accounting_oracle_member(member: str, quorum: int) -> Tuple[str, str]: 14 | hash_consensus: interface.LidoOracle = contracts.hash_consensus_for_accounting_oracle 15 | 16 | return (hash_consensus.address, hash_consensus.removeMember.encode_input(member, quorum)) 17 | 18 | 19 | def add_validators_exit_bus_oracle_member(member: str, quorum: int) -> Tuple[str, str]: 20 | hash_consensus: interface.LidoOracle = contracts.hash_consensus_for_validators_exit_bus_oracle 21 | 22 | return (hash_consensus.address, hash_consensus.addMember.encode_input(member, quorum)) 23 | 24 | 25 | def remove_validators_exit_bus_oracle_member(member: str, quorum: int) -> Tuple[str, str]: 26 | hash_consensus: interface.LidoOracle = contracts.hash_consensus_for_validators_exit_bus_oracle 27 | 28 | return (hash_consensus.address, hash_consensus.removeMember.encode_input(member, quorum)) 29 | -------------------------------------------------------------------------------- /utils/repo.py: -------------------------------------------------------------------------------- 1 | from utils.config import contracts 2 | 3 | 4 | def _add_implementation_to_repo(repo, version, address, content_uri): 5 | return (repo.address, repo.newVersion.encode_input(version, address, content_uri)) 6 | 7 | 8 | def add_implementation_to_lido_app_repo(version, address, content_uri): 9 | return _add_implementation_to_repo(contracts.lido_app_repo, version, address, content_uri) 10 | 11 | 12 | def add_implementation_to_nor_app_repo(version, address, content_uri): 13 | return _add_implementation_to_repo(contracts.nor_app_repo, version, address, content_uri) 14 | 15 | 16 | def add_implementation_to_sdvt_app_repo(version, address, content_uri): 17 | return _add_implementation_to_repo(contracts.simple_dvt_app_repo, version, address, content_uri) 18 | 19 | 20 | def add_implementation_to_sandbox_app_repo(version, address, content_uri): 21 | return _add_implementation_to_repo(contracts.sandbox_repo, version, address, content_uri) 22 | 23 | 24 | def add_implementation_to_voting_app_repo(version, address, content_uri): 25 | return _add_implementation_to_repo(contracts.voting_app_repo, version, address, content_uri) 26 | 27 | 28 | def add_implementation_to_oracle_app_repo(version, address, content_uri): 29 | return _add_implementation_to_repo(contracts.oracle_app_repo, version, address, content_uri) 30 | 31 | 32 | def create_new_app_repo(name, manager, version, address, content_uri): 33 | apm_registry = contracts.apm_registry 34 | 35 | return ( 36 | apm_registry.address, 37 | apm_registry.newRepoWithVersion.encode_input(name, manager, version, address, content_uri), 38 | ) 39 | -------------------------------------------------------------------------------- /utils/shapella_upgrade.py: -------------------------------------------------------------------------------- 1 | 2 | from utils.config import ( 3 | get_priority_fee, 4 | get_max_fee, 5 | get_is_live 6 | ) 7 | 8 | # Private constant taken from Lido contract 9 | INITIAL_TOKEN_HOLDER = "0x000000000000000000000000000000000000dEaD" 10 | 11 | TIMESTAMP_FIRST_SECOND_OF_JULY_2023_UTC = 1688169600 12 | 13 | 14 | def get_tx_params(deployer): 15 | tx_params = {"from": deployer} 16 | if get_is_live(): 17 | tx_params["priority_fee"] = get_priority_fee() 18 | tx_params["max_fee"] = get_max_fee() 19 | return tx_params 20 | -------------------------------------------------------------------------------- /utils/staking_module.py: -------------------------------------------------------------------------------- 1 | from brownie import reverts # type: ignore 2 | from brownie import convert 3 | from web3 import Web3 4 | 5 | from utils.config import ( 6 | contracts, 7 | ) 8 | 9 | def add_node_operator(staking_module, voting, stranger): 10 | operator_id = staking_module.getNodeOperatorsCount() 11 | 12 | with reverts("APP_AUTH_FAILED"): 13 | staking_module.addNodeOperator("test", f"0xbb{str(1).zfill(38)}", {"from": stranger} ) 14 | 15 | contracts.acl.grantPermission( 16 | stranger, 17 | staking_module, 18 | convert.to_uint(Web3.keccak(text="MANAGE_NODE_OPERATOR_ROLE")), 19 | {"from": voting}, 20 | ) 21 | 22 | staking_module.addNodeOperator("test", f"0xbb{str(1).zfill(38)}", {"from": stranger} ) 23 | 24 | return operator_id 25 | 26 | 27 | def calc_module_reward_shares(module_id, shares_minted_as_fees): 28 | distribution = contracts.staking_router.getStakingRewardsDistribution() 29 | module_idx = distribution[1].index(module_id) 30 | return distribution[2][module_idx] * shares_minted_as_fees // distribution[3] 31 | 32 | -------------------------------------------------------------------------------- /utils/test/event_validators/anchor.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | 3 | from brownie.network.event import EventDict 4 | from brownie import ZERO_ADDRESS 5 | from .common import validate_events_chain 6 | 7 | class TargetValidatorsCountChanged(NamedTuple): 8 | nodeOperatorId: int 9 | targetValidatorsCount: int 10 | 11 | def validate_anchor_vault_implementation_upgrade_events(events: EventDict, implementation: str, new_version: str): 12 | _events_chain = [ 13 | "LogScriptCall", "LogScriptCall", "Upgraded", "VersionIncremented", "EmergencyAdminChanged", "BridgeConnectorUpdated", 14 | "RewardsLiquidatorUpdated", "InsuranceConnectorUpdated", "AnchorRewardsDistributorUpdated", "LiquidationConfigUpdated", 15 | "ScriptResult", 16 | ] 17 | validate_events_chain([e.name for e in events], _events_chain) 18 | assert events.count("Upgraded") == 1 19 | assert events["Upgraded"]["implementation"] == implementation, "Wrong anchor vault proxy implementation" 20 | 21 | assert events.count("VersionIncremented") == 1 22 | assert events.count("EmergencyAdminChanged") == 1 23 | assert events["VersionIncremented"]["new_version"] == new_version, "Wrong anchor vault version" 24 | assert events["EmergencyAdminChanged"]["new_emergency_admin"] == ZERO_ADDRESS, "Wrong anchor vault emergency admin" 25 | assert events["BridgeConnectorUpdated"]["bridge_connector"] == ZERO_ADDRESS, "Wrong anchor vault bridge connector" 26 | assert events["RewardsLiquidatorUpdated"]["rewards_liquidator"] == ZERO_ADDRESS, "Wrong anchor vault rewards liquidator" 27 | assert events["InsuranceConnectorUpdated"]["insurance_connector"] == ZERO_ADDRESS, "Wrong anchor vault insurance connector" 28 | assert events["LiquidationConfigUpdated"]["liquidations_admin"] == ZERO_ADDRESS, "Wrong anchor vault liquidations admin" 29 | assert events["LiquidationConfigUpdated"]["no_liquidation_interval"] == 0, "Wrong anchor vault no_liquidation_interval" 30 | assert events["LiquidationConfigUpdated"]["restricted_liquidation_interval"] == 0, "Wrong anchor vault restricted_liquidation_interval" 31 | -------------------------------------------------------------------------------- /utils/test/event_validators/aragon.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Annotated 2 | from .common import validate_events_chain 3 | from brownie.network.event import EventDict 4 | 5 | 6 | def validate_app_update_event(event: EventDict, app_id: str, app_address: str): 7 | _ldo_events_chain = ["LogScriptCall", "SetApp"] 8 | 9 | validate_events_chain([e.name for e in event], _ldo_events_chain) 10 | 11 | assert event.count("SetApp") == 1 12 | 13 | assert event["SetApp"]["appId"] == app_id, "Wrong app id" 14 | assert event["SetApp"]["app"] == app_address, "Wrong app address" 15 | 16 | 17 | def validate_push_to_repo_event(event: EventDict, semantic_version: Annotated[Tuple[int, int, int], 3]): 18 | _ldo_events_chain = ["LogScriptCall", "NewVersion"] 19 | 20 | validate_events_chain([e.name for e in event], _ldo_events_chain) 21 | 22 | assert event.count("NewVersion") == 1 23 | 24 | assert event["NewVersion"]["semanticVersion"] == semantic_version, "Wrong version" 25 | -------------------------------------------------------------------------------- /utils/test/event_validators/burner.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | from brownie import ZERO_ADDRESS 3 | 4 | from brownie.network.event import EventDict 5 | from .common import validate_events_chain 6 | 7 | 8 | class StETH_burn_request(NamedTuple): 9 | requestedBy: str 10 | amountOfStETH: int 11 | amountOfShares: int 12 | isCover: bool 13 | 14 | 15 | def validate_steth_burn_requested_event(event: EventDict, b: StETH_burn_request): 16 | _events_chain = [ 17 | "LogScriptCall", 18 | "LogScriptCall", 19 | "Approval", 20 | "Transfer", 21 | "TransferShares", 22 | "StETHBurnRequested", 23 | "ScriptResult", 24 | ] 25 | 26 | validate_events_chain([e.name for e in event], _events_chain) 27 | 28 | assert event.count("LogScriptCall") == 2 29 | assert event.count("StETHBurnRequested") == 1 30 | 31 | assert event["StETHBurnRequested"]["amountOfStETH"] == b.amountOfStETH 32 | assert event["StETHBurnRequested"]["amountOfShares"] == b.amountOfShares 33 | assert event["StETHBurnRequested"]["requestedBy"] == b.requestedBy 34 | assert event["StETHBurnRequested"]["isCover"] == b.isCover 35 | -------------------------------------------------------------------------------- /utils/test/event_validators/common.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | #!/usr/bin/python3 4 | 5 | # Check that all tx_events contained in reference_events (with proper ordering and occurrences count). 6 | # Allow some of the reference_events skipped in the tx_events. 7 | # 8 | # Examples: 9 | # 10 | # tx_events: ['A', 'B', 'C'], reference_events: ['A', 'B', 'C'] => valid 11 | # tx_events: ['A', 'B', 'D'], reference_events: ['A', 'B', 'C', 'D'] => valid 12 | # 13 | # tx_events: ['A', 'B', 'D'], reference_events: ['A', 'B', 'C'] => invalid // extra 'D' event 14 | # tx_events: ['A', 'B', 'A', 'B'], reference_events: ['A', 'B'] => invalid // duplicated 'A', 'B' events chain 15 | # tx_events: ['A', 'C', 'B'], reference_events: ['A', 'B', 'C'] => invalid // wrong order 16 | def validate_events_chain(tx_events: List[str], reference_events: List[str]): 17 | for ev in tx_events: 18 | idx = next((reference_events.index(e) for e in reference_events if e == ev), len(reference_events)) 19 | assert idx != len(reference_events), f"{ev} not found in the remaining {reference_events} events chain" 20 | reference_events = reference_events[idx + 1 :] 21 | -------------------------------------------------------------------------------- /utils/test/event_validators/composite_post_rebase_beacon_receiver.py: -------------------------------------------------------------------------------- 1 | from brownie.network.event import EventDict 2 | from .common import validate_events_chain 3 | 4 | def validate_composite_receiver_callback_added_event(event: EventDict, callback: str, index: int): 5 | _events_chain = ['LogScriptCall', 'CallbackAdded'] 6 | 7 | validate_events_chain([e.name for e in event], _events_chain) 8 | 9 | assert event.count('CallbackAdded') == 1 10 | 11 | assert event['CallbackAdded']['callback'] == callback 12 | assert event['CallbackAdded']['atIndex'] == index -------------------------------------------------------------------------------- /utils/test/event_validators/csm.py: -------------------------------------------------------------------------------- 1 | from brownie.network.event import EventDict 2 | from .common import validate_events_chain 3 | from brownie import convert 4 | 5 | 6 | def validate_public_release_event(event: EventDict): 7 | _events_chain = ["LogScriptCall", "LogScriptCall", "PublicRelease", "ScriptResult"] 8 | validate_events_chain([e.name for e in event], _events_chain) 9 | assert event.count("PublicRelease") == 1 10 | 11 | 12 | def validate_set_key_removal_charge_event( 13 | event: EventDict, 14 | key_removal_charge: int, 15 | emitted_by: str | None = None, 16 | ): 17 | _events_chain = ["LogScriptCall", "LogScriptCall", "KeyRemovalChargeSet", "ScriptResult"] 18 | validate_events_chain([e.name for e in event], _events_chain) 19 | assert event.count("KeyRemovalChargeSet") == 1 20 | assert event["KeyRemovalChargeSet"]["amount"] == key_removal_charge 21 | 22 | if emitted_by is not None: 23 | event_emitted_by = convert.to_address(event["KeyRemovalChargeSet"]["_emitted_by"]) 24 | assert event_emitted_by == convert.to_address( 25 | emitted_by 26 | ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" 27 | -------------------------------------------------------------------------------- /utils/test/event_validators/dual_governance.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, List 2 | from web3 import Web3 3 | 4 | from brownie.network.event import EventDict 5 | 6 | from .common import validate_events_chain 7 | from brownie.network.transaction import TransactionReceipt 8 | from utils.test.tx_tracing_helpers import tx_events_from_trace 9 | 10 | 11 | def validate_dual_governance_submit_event( 12 | event: EventDict, proposal_id: int, proposer: str, executor: str, metadata: str, proposal_calls: any 13 | ) -> None: 14 | _events_chain = ["LogScriptCall", "ProposalSubmitted", "ProposalSubmitted"] 15 | 16 | validate_events_chain([e.name for e in event], _events_chain) 17 | assert event.count("LogScriptCall") == 1 18 | assert event.count("ProposalSubmitted") == 2 19 | 20 | assert event["ProposalSubmitted"][0]["id"] == proposal_id, "Wrong proposalId" 21 | assert event["ProposalSubmitted"][0]["executor"] == executor, "Wrong executor" 22 | 23 | # assert event["ProposalSubmitted"][0]["callsCount"] == len(proposal_calls), "Wrong callsCount" 24 | 25 | for i in range(1, len(proposal_calls)): 26 | assert event["ProposalSubmitted"][0]["calls"][i][0] == proposal_calls[i]["target"], "Wrong target" 27 | assert event["ProposalSubmitted"][0]["calls"][i][1] == proposal_calls[i]["value"], "Wrong value" 28 | assert event["ProposalSubmitted"][0]["calls"][i][2] == proposal_calls[i]["data"], "Wrong data" 29 | 30 | assert event["ProposalSubmitted"][1]["proposalId"] == proposal_id, "Wrong proposalId" 31 | assert event["ProposalSubmitted"][1]["proposerAccount"] == proposer, "Wrong proposer" 32 | assert event["ProposalSubmitted"][1]["metadata"] == metadata, "Wrong metadata" 33 | -------------------------------------------------------------------------------- /utils/test/event_validators/erc20_token.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | from brownie import ZERO_ADDRESS 3 | 4 | from brownie.network.event import EventDict 5 | from .common import validate_events_chain 6 | 7 | from utils.config import contracts 8 | 9 | 10 | class ERC20Approval(NamedTuple): 11 | owner: str 12 | spender: str 13 | amount: int 14 | 15 | 16 | class ERC20Transfer(NamedTuple): 17 | from_addr: str 18 | to_addr: str 19 | value: int 20 | 21 | 22 | def validate_erc20_approval_event(event: EventDict, a: ERC20Approval): 23 | _events_chain = ["LogScriptCall", "LogScriptCall", "Approval", "ScriptResult"] 24 | 25 | validate_events_chain([e.name for e in event], _events_chain) 26 | 27 | assert event.count("LogScriptCall") == 2 28 | assert event.count("Approval") == 1 29 | 30 | assert event["Approval"]["owner"] == a.owner 31 | assert event["Approval"]["spender"] == a.spender 32 | assert event["Approval"]["value"] == a.amount 33 | 34 | 35 | def validate_erc20_transfer_event(event: EventDict, t: ERC20Transfer, is_steth: bool = False): 36 | _events_chain = ["LogScriptCall", "LogScriptCall", "Transfer"] 37 | if is_steth: 38 | _events_chain += ["TransferShares"] 39 | _events_chain += ["ERC20Transferred", "ScriptResult"] 40 | 41 | validate_events_chain([e.name for e in event], _events_chain) 42 | 43 | assert event.count("LogScriptCall") == 2 44 | assert event.count("Transfer") == 1 45 | 46 | assert event["Transfer"]["from"] == t.from_addr 47 | assert event["Transfer"]["to"] == t.to_addr 48 | assert event["Transfer"]["value"] == t.value 49 | 50 | if is_steth: 51 | assert event.count("TransferShares") == 1 52 | 53 | assert event["TransferShares"]["from"] == t.from_addr 54 | assert event["TransferShares"]["to"] == t.to_addr 55 | assert event["TransferShares"]["sharesValue"] == contracts.lido.getSharesByPooledEth(t.value) 56 | -------------------------------------------------------------------------------- /utils/test/event_validators/hash_consensus.py: -------------------------------------------------------------------------------- 1 | from brownie.network.event import EventDict 2 | from .common import validate_events_chain 3 | from brownie import convert 4 | 5 | 6 | def validate_hash_consensus_member_removed(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None): 7 | _events_chain = ["LogScriptCall", "LogScriptCall", "MemberRemoved", "ScriptResult"] 8 | 9 | validate_events_chain([e.name for e in event], _events_chain) 10 | 11 | assert event.count("MemberRemoved") == 1 12 | 13 | assert event["MemberRemoved"]["addr"] == member 14 | assert event["MemberRemoved"]["newQuorum"] == new_quorum 15 | assert event["MemberRemoved"]["newTotalMembers"] == new_total_members 16 | if emitted_by is not None: 17 | event_emitted_by = convert.to_address(event["MemberRemoved"]["_emitted_by"]) 18 | assert event_emitted_by == convert.to_address( 19 | emitted_by 20 | ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" 21 | 22 | def validate_hash_consensus_member_added(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None): 23 | _events_chain = ["LogScriptCall", "LogScriptCall", "MemberAdded", "ScriptResult"] 24 | 25 | validate_events_chain([e.name for e in event], _events_chain) 26 | 27 | assert event.count("MemberAdded") == 1 28 | 29 | assert event["MemberAdded"]["addr"] == member 30 | assert event["MemberAdded"]["newQuorum"] == new_quorum 31 | assert event["MemberAdded"]["newTotalMembers"] == new_total_members 32 | if emitted_by is not None: 33 | event_emitted_by = convert.to_address(event["MemberAdded"]["_emitted_by"]) 34 | assert event_emitted_by == convert.to_address( 35 | emitted_by 36 | ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" 37 | -------------------------------------------------------------------------------- /utils/test/event_validators/oracle.py: -------------------------------------------------------------------------------- 1 | from brownie.network.event import EventDict 2 | from .common import validate_events_chain 3 | 4 | 5 | def validate_beacon_report_receiver_set_event(event: EventDict, callback: str): 6 | _events_chain = ["LogScriptCall", "BeaconReportReceiverSet"] 7 | 8 | validate_events_chain([e.name for e in event], _events_chain) 9 | 10 | assert event.count("BeaconReportReceiverSet") == 1 11 | 12 | assert event["BeaconReportReceiverSet"]["callback"] == callback 13 | 14 | 15 | def validate_oracle_member_added(event: EventDict, new_member: str): 16 | _events_chain = ["LogScriptCall", "MemberAdded"] 17 | 18 | validate_events_chain([e.name for e in event], _events_chain) 19 | 20 | assert event.count("MemberAdded") == 1 21 | 22 | assert event["MemberAdded"]["member"] == new_member 23 | 24 | 25 | def validate_oracle_quorum_changed(event: EventDict, new_quorum: int): 26 | _events_chain = ["LogScriptCall", "QuorumChanged"] 27 | 28 | validate_events_chain([e.name for e in event], _events_chain) 29 | 30 | assert event.count("QuorumChanged") == 1 31 | 32 | assert event["QuorumChanged"]["quorum"] == new_quorum 33 | -------------------------------------------------------------------------------- /utils/test/event_validators/proxy.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, List 2 | from web3 import Web3 3 | 4 | from brownie.network.event import EventDict 5 | 6 | from .common import validate_events_chain 7 | 8 | 9 | def validate_proxy_admin_changed(event: EventDict, prev_admin: str, new_admin: str) -> None: 10 | _events_chain = ["LogScriptCall", "AdminChanged"] 11 | 12 | validate_events_chain([e.name for e in event], _events_chain) 13 | 14 | assert event.count("LogScriptCall") == 1 15 | assert event.count("AdminChanged") == 1 16 | 17 | assert event["AdminChanged"]["previousAdmin"] == prev_admin, "Wrong previous admin" 18 | assert event["AdminChanged"]["newAdmin"] == new_admin, "Wrong new admin" 19 | -------------------------------------------------------------------------------- /utils/test/event_validators/relay_allowed_list.py: -------------------------------------------------------------------------------- 1 | from brownie.network.event import EventDict 2 | from .common import validate_events_chain 3 | from brownie import convert 4 | 5 | 6 | def validate_relay_allowed_list_manager_set(event: EventDict, new_manager: str, emitted_by: str | None = None): 7 | _events_chain = ["LogScriptCall", "LogScriptCall", "ManagerChanged", "ScriptResult"] 8 | 9 | validate_events_chain([e.name for e in event], _events_chain) 10 | 11 | assert event.count("ManagerChanged") == 1 12 | 13 | assert event["ManagerChanged"]["new_manager"] == new_manager 14 | if emitted_by is not None: 15 | event_emitted_by = convert.to_address(event["ManagerChanged"]["_emitted_by"]) 16 | assert event_emitted_by == convert.to_address( 17 | emitted_by 18 | ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" 19 | -------------------------------------------------------------------------------- /utils/test/event_validators/rewards_manager.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | 3 | from brownie.network.event import EventDict 4 | from .common import validate_events_chain 5 | 6 | class OwnershipTransferred(NamedTuple): 7 | previous_owner_addr: str 8 | new_owner_addr: str 9 | 10 | def validate_ownership_transferred_event(event: EventDict, ot: OwnershipTransferred): 11 | _events_chain = ['LogScriptCall', 'LogScriptCall', 'OwnershipTransferred', 'ScriptResult'] 12 | 13 | validate_events_chain([e.name for e in event], _events_chain) 14 | 15 | assert event.count('OwnershipTransferred') == 1 16 | 17 | assert event['OwnershipTransferred']['previousOwner'] == ot.previous_owner_addr 18 | assert event['OwnershipTransferred']['newOwner'] == ot.new_owner_addr 19 | -------------------------------------------------------------------------------- /utils/test/event_validators/time_constraints.py: -------------------------------------------------------------------------------- 1 | from brownie.network.event import EventDict 2 | 3 | from .common import validate_events_chain 4 | 5 | 6 | def validate_time_constraints_executed_before_event(event: EventDict, timestamp) -> None: 7 | _events_chain = ["LogScriptCall", "TimeBeforeTimestampChecked"] 8 | 9 | validate_events_chain([e.name for e in event], _events_chain) 10 | assert event.count("LogScriptCall") == 1 11 | assert event.count("TimeBeforeTimestampChecked") == 1 12 | assert event["TimeBeforeTimestampChecked"][0]["timestamp"] == timestamp 13 | 14 | 15 | def validate_dg_time_constraints_executed_before_event(event: EventDict, timestamp) -> None: 16 | _events_chain = ["TimeBeforeTimestampChecked", "Executed"] 17 | 18 | validate_events_chain([e.name for e in event], _events_chain) 19 | assert event.count("Executed") == 1 20 | assert event.count("TimeBeforeTimestampChecked") == 1 21 | assert event["TimeBeforeTimestampChecked"][0]["timestamp"] == timestamp 22 | 23 | 24 | def validate_dg_time_constraints_executed_with_day_time_event(event: EventDict, start_day_time, end_day_time) -> None: 25 | _events_chain = ["TimeWithinDayTimeChecked", "Executed"] 26 | 27 | validate_events_chain([e.name for e in event], _events_chain) 28 | assert event.count("Executed") == 1 29 | assert event.count("TimeWithinDayTimeChecked") == 1 30 | assert event["TimeWithinDayTimeChecked"][0]["startDayTime"] == start_day_time 31 | assert event["TimeWithinDayTimeChecked"][0]["endDayTime"] == end_day_time 32 | -------------------------------------------------------------------------------- /utils/test/event_validators/token_manager.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | from brownie import ZERO_ADDRESS 3 | 4 | from brownie.network.event import EventDict 5 | from .common import validate_events_chain 6 | 7 | class Issue(NamedTuple): 8 | token_manager_addr: str 9 | amount: int 10 | 11 | class Vested(NamedTuple): 12 | destination_addr: str 13 | amount: int 14 | start: int 15 | cliff: int 16 | vesting: int 17 | revokable: bool 18 | 19 | def validate_ldo_issue_event(event: EventDict, i: Issue): 20 | _events_chain = ['LogScriptCall', 'Transfer'] 21 | 22 | validate_events_chain([e.name for e in event], _events_chain) 23 | 24 | assert event.count('LogScriptCall') == 1 25 | assert event.count('Transfer') == 1 26 | 27 | assert event['Transfer']['from'] == ZERO_ADDRESS, "Wrong from field" 28 | assert event['Transfer']['to'] == i.token_manager_addr 29 | assert event['Transfer']['value'] == i.amount 30 | 31 | def validate_ldo_vested_event(event: EventDict, v: Vested): 32 | _events_chain = ['LogScriptCall', 'Transfer', 'NewVesting'] 33 | 34 | assert event.count('LogScriptCall') == 1 35 | assert event.count('Transfer') == 1 36 | assert event.count('NewVesting') == 1 37 | 38 | validate_events_chain([e.name for e in event], _events_chain) 39 | 40 | assert event['Transfer']['to'] == v.destination_addr 41 | assert event['Transfer']['value'] == v.amount 42 | 43 | assert event['NewVesting']['receiver'] == v.destination_addr 44 | assert event['NewVesting']['amount'] == v.amount 45 | -------------------------------------------------------------------------------- /utils/test/event_validators/tokens_recoverer.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | 3 | from brownie.network.event import EventDict 4 | from .common import validate_events_chain 5 | 6 | class Recover(NamedTuple): 7 | sender_addr: str 8 | manager_addr: str 9 | token_addr: str 10 | amount: int 11 | recovered_amount: int 12 | 13 | def validate_recover_event(event: EventDict, r: Recover, dao_agent: str): 14 | _events_chain = ['LogScriptCall', 'Transfer', 'ERC20TokenRecovered', 'OwnershipTransferred', 'Recover'] 15 | 16 | validate_events_chain([e.name for e in event], _events_chain) 17 | 18 | assert event.count('Recover') == 1 19 | assert event.count('ERC20TokenRecovered') == 1 20 | assert event.count('OwnershipTransferred') == 1 21 | assert event.count('Transfer') == 1 22 | 23 | assert event['Transfer']['from'] == r.manager_addr 24 | assert event['Transfer']['to'] == dao_agent 25 | assert event['Transfer']['value'] == r.recovered_amount 26 | 27 | assert event['ERC20TokenRecovered']['token'] == r.token_addr 28 | assert event['ERC20TokenRecovered']['amount'] == r.recovered_amount 29 | assert event['ERC20TokenRecovered']['recipient'] == dao_agent 30 | 31 | assert event['OwnershipTransferred']['newOwner'] == dao_agent 32 | 33 | assert event['Recover']['sender'] == r.sender_addr 34 | assert event['Recover']['manager'] == r.manager_addr 35 | assert event['Recover']['token'] == r.token_addr 36 | assert event['Recover']['amount'] == r.amount 37 | assert event['Recover']['recovered_amount'] == r.recovered_amount 38 | -------------------------------------------------------------------------------- /utils/test/event_validators/unpause.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from typing import NamedTuple 4 | 5 | from brownie.network.event import EventDict 6 | from .common import validate_events_chain 7 | 8 | 9 | def validate_unpause_event(event: EventDict): 10 | # extra 'LogScriptCall' and 'ScriptResult' due to agent forwarding 11 | _unpause_events_chain = ['LogScriptCall', 'LogScriptCall', 'DepositsUnpaused', 'ScriptResult'] 12 | 13 | validate_events_chain([e.name for e in event], _unpause_events_chain) 14 | 15 | assert event.count('DepositsUnpaused') == 1 16 | -------------------------------------------------------------------------------- /utils/test/event_validators/vault.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | from brownie import ZERO_ADDRESS 3 | 4 | from brownie.network.event import EventDict 5 | from .common import validate_events_chain 6 | 7 | class transferERC20(NamedTuple): 8 | token_addr: str 9 | from_addr: str 10 | to_addr: str 11 | amount: int 12 | 13 | def validate_transferERC20_event(event: EventDict, t: transferERC20, is_steth: bool = False): 14 | _token_events_chain = ["LogScriptCall", "LogScriptCall", "Transfer"] 15 | if is_steth: 16 | _token_events_chain += ["TransferShares"] 17 | 18 | _token_events_chain += ["ERC20Transferred", "ScriptResult"] 19 | 20 | validate_events_chain([e.name for e in event], _token_events_chain) 21 | 22 | assert event.count("LogScriptCall") == 2 23 | assert event.count("Transfer") == 1 24 | assert event.count("ERC20Transferred") == 1 25 | 26 | _to = _from = ZERO_ADDRESS 27 | _value = 0 28 | try: 29 | _to = event["Transfer"]["to"] 30 | _from = event["Transfer"]["from"] 31 | _value = event["Transfer"]["value"] 32 | except: 33 | _to = event["Transfer"]["_to"] 34 | _from = event["Transfer"]["_from"] 35 | _value = event["Transfer"]["_amount"] 36 | 37 | assert _from == t.from_addr, "Wrong payout source ('from')" 38 | assert _to == t.to_addr, "Wrong payout destination ('to')" 39 | assert _value == t.amount, "Wrong payout amount" 40 | 41 | assert event["ERC20Transferred"]["_token"] == t.token_addr 42 | assert event["ERC20Transferred"]["_recipient"] == t.to_addr 43 | assert event["ERC20Transferred"]["_amount"] == t.amount -------------------------------------------------------------------------------- /utils/test/event_validators/vesting_escrow.py: -------------------------------------------------------------------------------- 1 | from .common import validate_events_chain 2 | from brownie.network.event import EventDict 3 | 4 | 5 | def validate_voting_adapter_upgraded_event(event: EventDict, voting_adapter_address: str): 6 | _events_chain = ["LogScriptCall", "LogScriptCall", "VotingAdapterUpgraded", "ScriptResult"] 7 | validate_events_chain([e.name for e in event], _events_chain) 8 | 9 | assert event.count("VotingAdapterUpgraded") == 1 10 | 11 | assert event["VotingAdapterUpgraded"]["voting_adapter"] == voting_adapter_address, "Wrong voting adapter address" 12 | -------------------------------------------------------------------------------- /utils/test/event_validators/voting.py: -------------------------------------------------------------------------------- 1 | from .common import validate_events_chain 2 | from brownie.network.event import EventDict 3 | 4 | 5 | def validate_change_vote_time_event(event: EventDict, vote_time: int): 6 | _ldo_events_chain = ["LogScriptCall", "ChangeVoteTime"] 7 | 8 | validate_events_chain([e.name for e in event], _ldo_events_chain) 9 | 10 | assert event.count("ChangeVoteTime") == 1 11 | 12 | assert event["ChangeVoteTime"]["voteTime"] == vote_time, "Wrong voteTime" 13 | 14 | 15 | def validate_change_objection_time_event(event: EventDict, objection_time: int): 16 | _ldo_events_chain = ["LogScriptCall", "ChangeObjectionPhaseTime"] 17 | 18 | validate_events_chain([e.name for e in event], _ldo_events_chain) 19 | 20 | assert event.count("ChangeObjectionPhaseTime") == 1 21 | 22 | assert event["ChangeObjectionPhaseTime"]["objectionPhaseTime"] == objection_time, "Wrong objectionPhaseTime" 23 | -------------------------------------------------------------------------------- /utils/test/helpers.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from hexbytes import HexBytes 4 | from brownie import web3 5 | from web3.types import BlockIdentifier # type: ignore 6 | 7 | from utils.config import contracts 8 | 9 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 10 | GWEI = 10**9 11 | ONE_ETH = 10**18 12 | ZERO_HASH = bytes([0] * 32) 13 | ZERO_BYTES32 = HexBytes(ZERO_HASH) 14 | 15 | 16 | def ETH(amount): 17 | return math.floor(amount * 10**18) 18 | 19 | 20 | def SHARES(amount): 21 | return ETH(amount) 22 | 23 | 24 | def steth_balance(account): 25 | return contracts.lido.balanceOf(account) 26 | 27 | 28 | def eth_balance(account: str, block_identifier: BlockIdentifier = "latest"): 29 | return web3.eth.get_balance(account, block_identifier=block_identifier) 30 | 31 | 32 | def almostEqEth(b1, b2): 33 | return abs(b1 - b2) <= 10 34 | 35 | 36 | def shares_balance(account): 37 | return contracts.lido.sharesOf(account) 38 | 39 | 40 | def almostEqWithDiff(b1, b2, diff): 41 | return abs(b1 - b2) <= diff 42 | 43 | def round_to_gwei(amount: int) -> int: 44 | """Round amount to gwei""" 45 | return amount // GWEI * GWEI 46 | 47 | -------------------------------------------------------------------------------- /utils/test/keys_helpers.py: -------------------------------------------------------------------------------- 1 | import random 2 | import textwrap 3 | 4 | PUBKEY_LENGTH = 48 5 | SIGNATURE_LENGTH = 96 6 | 7 | 8 | def random_pubkeys_batch(pubkeys_count: int): 9 | return random_hexstr(pubkeys_count * PUBKEY_LENGTH) 10 | 11 | 12 | def random_signatures_batch(signautes_count: int): 13 | return random_hexstr(signautes_count * SIGNATURE_LENGTH) 14 | 15 | 16 | def parse_pubkeys_batch(pubkeys_batch: str): 17 | return hex_chunks(pubkeys_batch, PUBKEY_LENGTH) 18 | 19 | 20 | def parse_signatures_batch(signatures_batch: str): 21 | return hex_chunks(signatures_batch, SIGNATURE_LENGTH) 22 | 23 | 24 | def hex_chunks(hexstr: str, chunk_length: int): 25 | stripped_hexstr = strip_0x(hexstr) 26 | assert len(stripped_hexstr) % chunk_length == 0, "invalid hexstr length" 27 | return [prefix_0x(chunk) for chunk in textwrap.wrap(stripped_hexstr, 2 * chunk_length)] 28 | 29 | 30 | def random_hexstr(length: int): 31 | return prefix_0x(random.randbytes(length).hex()) 32 | 33 | 34 | def prefix_0x(hexstr: str): 35 | return hexstr if hexstr.startswith("0x") else "0x" + hexstr 36 | 37 | 38 | def strip_0x(hexstr: str): 39 | return hexstr[2:] if hexstr.startswith("0x") else hexstr 40 | -------------------------------------------------------------------------------- /utils/test/legacy_oracle_report_helpers.py: -------------------------------------------------------------------------------- 1 | from brownie import interface, accounts 2 | from utils.config import contracts, lido_dao_legacy_oracle 3 | 4 | 5 | def legacy_report(_epochId, _beaconBalance, _beaconValidators): 6 | oracle = interface.LidoOracle(lido_dao_legacy_oracle) 7 | quorum = oracle.getQuorum() 8 | members = oracle.getOracleMembers() 9 | 10 | for i in range(quorum): 11 | member = accounts.at(members[i], force=True) 12 | oracle.reportBeacon(_epochId, _beaconBalance, _beaconValidators, {"from": member}) 13 | -------------------------------------------------------------------------------- /utils/test/staking_router_helpers.py: -------------------------------------------------------------------------------- 1 | from brownie import web3 2 | from utils.config import contracts 3 | from enum import IntEnum 4 | 5 | class StakingModuleStatus(IntEnum): 6 | Active = 0 7 | DepositsPaused = 1 8 | Stopped = 2 9 | 10 | def set_staking_module_status(module_id, staking_module_status: StakingModuleStatus): 11 | contracts.staking_router.setStakingModuleStatus(module_id, staking_module_status, {"from": contracts.agent}) 12 | 13 | 14 | def increase_staking_module_share(module_id, share_multiplier): 15 | module = contracts.staking_router.getStakingModule(module_id) 16 | 17 | contracts.staking_router.updateStakingModule( 18 | module_id, 19 | module["stakeShareLimit"] * share_multiplier, 20 | module["priorityExitShareThreshold"] * share_multiplier, 21 | module["stakingModuleFee"], 22 | module["treasuryFee"], 23 | module["maxDepositsPerBlock"], 24 | module["minDepositBlockDistance"], 25 | {"from": contracts.agent}, 26 | ) 27 | -------------------------------------------------------------------------------- /utils/test/tx_cost_helper.py: -------------------------------------------------------------------------------- 1 | def transaction_cost(tx): 2 | gas_used = tx.gas_used 3 | gas_price = tx.gas_price 4 | return gas_used * gas_price 5 | -------------------------------------------------------------------------------- /utils/txs/deploy.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def deploy_from_prepared_tx(deployer, tx_file_path) -> str: 5 | tx_data = json.load(open(tx_file_path))["data"] 6 | tx = deployer.transfer(data=tx_data) 7 | return tx.contract_address 8 | 9 | -------------------------------------------------------------------------------- /utils/withdrawal_credentials.py: -------------------------------------------------------------------------------- 1 | 2 | ETH1_ADDRESS_WITHDRAWAL_PREFIX = '01' 3 | 4 | 5 | def strip_byte_prefix(hexstr): 6 | return hexstr[2:] if hexstr[0:2] == '0x' else hexstr 7 | 8 | 9 | def get_eth1_withdrawal_credentials(withdrawal_contract_address): 10 | wc = '0x' + ETH1_ADDRESS_WITHDRAWAL_PREFIX + '00' * 11 + strip_byte_prefix(withdrawal_contract_address) 11 | return wc.lower() 12 | 13 | 14 | def encode_set_withdrawal_credentials(withdrawal_credentials, lido): 15 | return ( 16 | lido.address, 17 | lido.setWithdrawalCredentials.encode_input(withdrawal_credentials) 18 | ) 19 | 20 | 21 | def extract_address_from_eth1_wc(withdrawal_credentials): 22 | striped_wc = strip_byte_prefix(withdrawal_credentials) 23 | 24 | if len(striped_wc) != 64: 25 | raise Exception('withdrawal_contract has a not valid length') 26 | 27 | prefix = striped_wc[0:2] 28 | zero_pad = striped_wc[2:24] 29 | address = striped_wc[24:] 30 | 31 | if prefix != '01': 32 | raise Exception('withdrawal_contract has not valid eth1 prefix') 33 | 34 | if zero_pad != '0000000000000000000000': 35 | raise Exception('withdrawal_contract has not valid zero_pad') 36 | 37 | return '0x' + address 38 | 39 | 40 | def colorize_withdrawal_credentials(withdrawal_credentials): 41 | byte_prefix = withdrawal_credentials[:2] 42 | prefix = withdrawal_credentials[2:4] 43 | zero_pad = withdrawal_credentials[4:26] 44 | address = withdrawal_credentials[26:] 45 | 46 | hl_color = '\x1b[38;5;141m' 47 | yellow_color = '\x1b[0;33m' 48 | gray_color = '\x1b[0;m' 49 | end_of_color = '\033[0m' 50 | 51 | return f'{hl_color}{byte_prefix}{yellow_color}{prefix}{gray_color}{zero_pad}{hl_color}{address}{end_of_color}' 52 | --------------------------------------------------------------------------------