diff --git a/.github/workflows/core_tests.yml b/.github/workflows/core_tests.yml index bafb16dbc..1ab431351 100644 --- a/.github/workflows/core_tests.yml +++ b/.github/workflows/core_tests.yml @@ -9,6 +9,7 @@ on: - "feat/rc2" - "feat/rc1" - "feat/next-vote" + - "feat/v3-vote" schedule: - cron: "0 0 * * TUE" @@ -19,11 +20,10 @@ jobs: timeout-minutes: 120 services: - tests-runner: - image: ghcr.io/lidofinance/scripts:v19 + image: ghcr.io/lidofinance/scripts:v21 ports: - - 8546:8546 + - 8545:8545 volumes: - ${{ github.workspace }}:/root/scripts options: >- @@ -34,25 +34,38 @@ jobs: uses: actions/checkout@v4 - name: Run init script - run: docker exec -e CORE_BRANCH tests-runner bash -c 'make init' + run: docker exec -e CORE_BRANCH tests-runner bash -c 'PYTHONPATH=$PWD make init' env: - CORE_BRANCH: master + CORE_BRANCH: develop - name: Run node - run: docker exec -e ETH_RPC_URL --detach tests-runner bash -c 'NODE_PORT=8546 make node' + run: docker exec -e ETH_RPC_URL --detach tests-runner bash -c 'NODE_PORT=8545 make node' env: ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} - name: Check that the fork is ready shell: bash - run: timeout 30 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' 127.0.0.1 8546 + run: | + echo "Waiting for fork node on 127.0.0.1:8545..." + sleep 10 + for i in {1..30}; do + if (echo > /dev/tcp/127.0.0.1/8545) >/dev/null 2>&1; then + echo "Fork is ready ✅" + exit 0 + fi + echo "Not ready yet... ($i/30)" + sleep 1 + done + echo "❌ Fork was not ready after 30 seconds" >&2 + exit 1 - name: Prepare test environment with Brownie - run: docker exec -e GITHUB_TOKEN -e ETH_RPC_URL tests-runner bash -c 'make ci-prepare-environment' + run: docker exec -e GITHUB_TOKEN -e ETH_RPC_URL -e ETHERSCAN_TOKEN tests-runner bash -c 'make ci-prepare-environment' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ETHERSCAN_TOKEN: ${{ secrets.ETHERSCAN_TOKEN }} - name: Run core tests run: docker exec -e CORE_TESTS_TARGET_RPC_URL tests-runner bash -c 'make test-core' env: - CORE_TESTS_TARGET_RPC_URL: http://127.0.0.1:8546 + CORE_TESTS_TARGET_RPC_URL: http://127.0.0.1:8545 diff --git a/.github/workflows/dual_governance_regression.yml b/.github/workflows/dual_governance_regression.yml index 0e2bf8aff..6cbf75777 100644 --- a/.github/workflows/dual_governance_regression.yml +++ b/.github/workflows/dual_governance_regression.yml @@ -13,6 +13,7 @@ on: - "feat/rc2" - "feat/rc1" - "feat/next-vote" + - "feat/v3-vote" workflow_dispatch: jobs: @@ -26,15 +27,14 @@ jobs: cancel-in-progress: true env: - NODE_VERSION: '20' - PYTHON_VERSION: '3.10' - POETRY_VERSION: '1.8.2' - HARDHAT_NODE_URL: 'http://127.0.0.1:8545' - ANVIL_NODE_URL: 'http://127.0.0.1:8555' + NODE_VERSION: "20" + PYTHON_VERSION: "3.10" + POETRY_VERSION: "1.8.2" + HARDHAT_NODE_URL: "http://127.0.0.1:8545" + ANVIL_NODE_URL: "http://127.0.0.1:8555" CURL_PARAMS: '-X POST -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[],\"id\":1}"' steps: - ################################################################ # Clone repositories` ################################################################ @@ -50,7 +50,7 @@ jobs: uses: actions/checkout@v4 with: repository: lidofinance/dual-governance - ref: feature/escrow-mainnet-deploy + ref: feature/report-simulation path: dual-governance persist-credentials: false token: ${{ secrets.GITHUB_TOKEN }} @@ -63,7 +63,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} - cache: 'yarn' + cache: "yarn" cache-dependency-path: scripts/yarn.lock - name: Install Poetry @@ -141,11 +141,17 @@ jobs: done working-directory: scripts + - name: Import network config for brownie + shell: bash + run: poetry run brownie networks import network-config.yaml True + working-directory: scripts + - name: Prepare test environment with Brownie - run: poetry run brownie run scripts/ci/prepare_environment --network mainnet-fork + run: PYTHONPATH=$PWD poetry run brownie run scripts/ci/prepare_environment --network mfh-1 working-directory: scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ETHERSCAN_TOKEN: ${{ secrets.ETHERSCAN_TOKEN }} - name: Run regression tests run: npm run test:regressions -- --load-accounts diff --git a/.github/workflows/large_vote_ci.yml b/.github/workflows/large_vote_ci.yml index 004819bf2..d125956dc 100644 --- a/.github/workflows/large_vote_ci.yml +++ b/.github/workflows/large_vote_ci.yml @@ -36,7 +36,7 @@ jobs: vote: "large" rpc_url: ${{ secrets.ETH_RPC_URL }} etherscan: ${{ secrets.ETHERSCAN_TOKEN }} - command: "make test-1/2" + command: "PYTHONPATH=$PWD make test-1/2" run-tests-large-2: name: Brownie fork LARGE tests 2 @@ -62,4 +62,4 @@ jobs: vote: "large" rpc_url: ${{ secrets.ETH_RPC_URL }} etherscan: ${{ secrets.ETHERSCAN_TOKEN }} - command: "make test-2/2" + command: "PYTHONPATH=$PWD make test-2/2" diff --git a/.github/workflows/normal_vote_ci.yml b/.github/workflows/normal_vote_ci.yml index 650d57bb7..b810c9b27 100644 --- a/.github/workflows/normal_vote_ci.yml +++ b/.github/workflows/normal_vote_ci.yml @@ -13,7 +13,16 @@ jobs: run-tests-normal-1: name: Brownie fork NORMAL tests 1 runs-on: "ubuntu-latest" - timeout-minutes: 100 + timeout-minutes: 150 + + services: + hardhat-node: + image: ghcr.io/lidofinance/hardhat-node:2.26.0 + ports: + - 8545:8545 + env: + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} + HARDFORK: "cancun" steps: - uses: actions/checkout@v4 @@ -23,12 +32,21 @@ jobs: vote: "normal" rpc_url: ${{ secrets.ETH_RPC_URL }} etherscan: ${{ secrets.ETHERSCAN_TOKEN }} - command: "make test-1/2" + command: "PYTHONPATH=$PWD make test-1/2" run-tests-normal-2: name: Brownie fork NORMAL tests 2 runs-on: "ubuntu-latest" - timeout-minutes: 100 + timeout-minutes: 150 + + services: + hardhat-node: + image: ghcr.io/lidofinance/hardhat-node:2.26.0 + ports: + - 8546:8545 + env: + ETH_RPC_URL: ${{ secrets.ETH_RPC_URL }} + HARDFORK: "cancun" steps: - uses: actions/checkout@v4 @@ -38,4 +56,4 @@ jobs: vote: "normal" rpc_url: ${{ secrets.ETH_RPC_URL }} etherscan: ${{ secrets.ETHERSCAN_TOKEN }} - command: "make test-2/2" + command: "PYTHONPATH=$PWD make test-2/2" diff --git a/Dockerfile b/Dockerfile index c6aa75b08..b461f66d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -233,6 +233,40 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then \ git clean -d -x -f; \ fi +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + # build solc-v0.8.25 + git checkout v0.8.25; \ + # the compiler throws warnings when compiling this version, and the warnings are treated as errors. + # we disable treating the warnings as errors, unless the build doesn't succeed + grep -rl '\-Werror' ./cmake/EthCompilerSettings.cmake | xargs sed -i 's/\-Werror/\-Wno\-error/g'; \ + # there is no sudo in the container, but we are under root so we do not need it + grep -rl 'sudo make install' ./scripts/build.sh | xargs sed -i 's/sudo make install/make install/g'; \ + # build solc faster + grep -rl 'make -j2' ./scripts/build.sh | xargs sed -i 's/make -j2/make -j4/g'; \ + ./scripts/build.sh; \ + mv /usr/local/bin/solc /root/.solcx/solc-v0.8.25; \ + git checkout .; \ + git checkout develop; \ + git clean -d -x -f; \ + fi + +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + # build solc-v0.8.21 + git checkout v0.8.21; \ + # the compiler throws warnings when compiling this version, and the warnings are treated as errors. + # we disable treating the warnings as errors, unless the build doesn't succeed + grep -rl '\-Werror' ./cmake/EthCompilerSettings.cmake | xargs sed -i 's/\-Werror/\-Wno\-error/g'; \ + # there is no sudo in the container, but we are under root so we do not need it + grep -rl 'sudo make install' ./scripts/build.sh | xargs sed -i 's/sudo make install/make install/g'; \ + # build solc faster + grep -rl 'make -j2' ./scripts/build.sh | xargs sed -i 's/make -j2/make -j4/g'; \ + ./scripts/build.sh; \ + mv /usr/local/bin/solc /root/.solcx/solc-v0.8.21; \ + git checkout .; \ + git checkout develop; \ + git clean -d -x -f; \ + fi + RUN if [ "$TARGETARCH" = "arm64" ]; then \ # build solc-v0.6.11 git checkout v0.6.11; \ @@ -303,6 +337,8 @@ RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.7.6 --version | gr RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.15 --version | grep 'Version: 0.8.15+commit.e14f2714' || (echo "Incorrect solc-v0.8.15 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.19 --version | grep 'Version: 0.8.19+commit.7dd6d404' || (echo "Incorrect solc-v0.8.19 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.24 --version | grep 'Version: 0.8.24+commit.e11b9ed9' || (echo "Incorrect solc-v0.8.24 version" && exit 1) fi +RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.25 --version | grep 'Version: 0.8.25+commit.b61c2a91' || (echo "Incorrect solc-v0.8.25 version" && exit 1) fi +RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.8.21 --version | grep 'Version: 0.8.21+commit.d9974bed' || (echo "Incorrect solc-v0.8.21 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.solcx/solc-v0.6.11 --version | grep 'Version: 0.6.11+commit.5ef660b1' || (echo "Incorrect solc-v0.6.11 version" && exit 1) fi RUN if [ "$TARGETARCH" = "arm64" ]; then /root/.vvm/vyper-0.3.7 --version | grep '0.3.7+' || (echo "Incorrect vyper-0.3.7 version" && exit 1) fi diff --git a/Makefile b/Makefile index e870ae11a..f9a252d6c 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,13 @@ define run_2nd_test poetry run $(1) endef +# Get the latest block number from the target RPC node to use as FORKING_BLOCK_NUMBER for core tests +__get_rpc_latest_block_number: + @curl -s -X POST $(CORE_TESTS_TARGET_RPC_URL) \ + -H "Content-Type: application/json" \ + --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ + | sed -E 's/.*"result":"([^"]+)".*/\1/' \ + | xargs printf "%d" test: ifdef vote @@ -22,6 +29,7 @@ CORE_DIR ?= lido-core CORE_BRANCH ?= master NODE_PORT ?= 8545 SECONDARY_NETWORK ?= mfh-2 +NETWORK_STATE_FILE ?= deployed-mainnet.json test-1/2: poetry run brownie test tests/[tc]*.py tests/regression/test_staking_router_stake_distribution.py --durations=20 --network mfh-1 @@ -36,8 +44,8 @@ init-scripts: # because current brownie version does not support pm install from the config file poetry install && \ yarn && \ - poetry run brownie pm install OpenZeppelin/openzeppelin-contracts@4.0.0 && \ - poetry run brownie compile && \ + PYTHONPATH=$$PWD poetry run brownie pm install OpenZeppelin/openzeppelin-contracts@4.0.0 && \ + PYTHONPATH=$$PWD poetry run brownie compile && \ poetry run brownie networks import network-config.yaml True debug: @@ -78,9 +86,12 @@ node3: npx hardhat node --fork $(ETH_RPC_URL3) --port $(NODE_PORT) test-core: + LATEST_BLOCK_NUMBER=$$($(MAKE) --no-print-directory __get_rpc_latest_block_number) && \ + echo "LATEST_BLOCK_NUMBER: $$LATEST_BLOCK_NUMBER" && \ cd $(CORE_DIR) && \ - FORK_RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ RPC_URL=$(CORE_TESTS_TARGET_RPC_URL) \ + NETWORK_STATE_FILE=$(NETWORK_STATE_FILE) \ + FORKING_BLOCK_NUMBER=$$LATEST_BLOCK_NUMBER \ yarn test:integration slots: @@ -92,7 +103,7 @@ slots: @rm -f slots.ts ci-prepare-environment: - poetry run brownie run scripts/ci/prepare_environment --network $(SECONDARY_NETWORK) + poetry run brownie run scripts/ci/prepare_environment --network mfh-1 enact-fork: poetry run brownie run $(vote) start_and_execute_vote_on_fork_manual --network=mfh-1 diff --git a/README.md b/README.md index db3f5e578..994cee693 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ _*may be optionally set when running tests asynchronously to reduce the risk of Run the container in the `scripts` directory and specify the ENV VARs: ```shell -docker run --name scripts -v "$(pwd)":/root/scripts -e ETH_RPC_URL -e ETH_RPC_URL2 -e ETH_RPC_URL3 -e PINATA_CLOUD_TOKEN -e DEPLOYER -e ETHERSCAN_TOKEN -e ETHERSCAN_TOKEN2 -e ETHERSCAN_TOKEN3 -d ghcr.io/lidofinance/scripts:v19 +docker run --name scripts -v "$(pwd)":/root/scripts -e ETH_RPC_URL -e ETH_RPC_URL2 -e PINATA_CLOUD_TOKEN -e DEPLOYER -e ETHERSCAN_TOKEN -e ETHERSCAN_TOKEN2 -p 8545:8545 -d ghcr.io/lidofinance/scripts:v21 ``` #### Step 4. Initialize container diff --git a/brownie-config.yml b/brownie-config.yml index 98db6cfbe..09ffaa9d3 100644 --- a/brownie-config.yml +++ b/brownie-config.yml @@ -9,7 +9,7 @@ networks: live: priority_fee: auto -autofetch_sources: true +autofetch_sources: false hypothesis: max_examples: 10 diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index 40768f64d..5c63a9cd2 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -99,6 +99,7 @@ USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" WETH_TOKEN = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" +MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" # # Lido V2 upgrade entries @@ -107,7 +108,7 @@ # LidoLocator LIDO_LOCATOR = "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" -LIDO_LOCATOR_IMPL = "0x2C298963FB763f74765829722a1ebe0784f4F5Cf" +LIDO_LOCATOR_IMPL = "0x2f8779042EFaEd4c53db2Ce293eB6B3f7096C72d" # Other upgrade addresses LIDO_V2_UPGRADE_TEMPLATE = "0xa818fF9EC93122Bf9401ab4340C42De638CD600a" @@ -120,7 +121,7 @@ WITHDRAWAL_CREDENTIALS = "0x010000000000000000000000b9d7934878b5fb9610b3fe8a5e441e8fad7e293f" # Lido -LIDO_IMPL = "0x17144556fd3424EDC8Fc8A4C940B2D04936d17eb" +LIDO_IMPL = "0x6ca84080381E43938476814be61B779A8bB6a600" # see Lido's proxy appId() LIDO_ARAGON_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" LIDO_MAX_STAKE_LIMIT_ETH = 150_000 @@ -140,8 +141,8 @@ CURATED_STAKING_MODULE_STUCK_PENALTY_DELAY = 0 CURATED_STAKING_MODULE_TARGET_SHARE_BP = 10000 -CURATED_STAKING_MODULE_MODULE_FEE_BP = 500 -CURATED_STAKING_MODULE_TREASURY_FEE_BP = 500 +CURATED_STAKING_MODULE_MODULE_FEE_BP = 350 +CURATED_STAKING_MODULE_TREASURY_FEE_BP = 650 CURATED_STAKING_MODULE_ID = 1 CURATED_STAKING_MODULE_NAME = "curated-onchain-v1" CURATED_STAKING_MODULE_TYPE = ( @@ -161,7 +162,7 @@ SIMPLE_DVT_ARAGON_APP_NAME = "simple-dvt" SIMPLE_DVT_ARAGON_APP_ID = "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4" SIMPLE_DVT_MODULE_STUCK_PENALTY_DELAY = 0 -SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 400 +SIMPLE_DVT_MODULE_TARGET_SHARE_BP = 430 SIMPLE_DVT_MODULE_MODULE_FEE_BP = 800 SIMPLE_DVT_MODULE_TREASURY_FEE_BP = 200 SIMPLE_DVT_MODULE_ID = 2 @@ -171,7 +172,7 @@ "0x637572617465642d6f6e636861696e2d76310000000000000000000000000000" ) SIMPLE_DVT_VERSION = 4 -SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 444 +SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 478 SIMPLE_DVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 SIMPLE_DVT_MODULE_MIN_DEPOSITS_BLOCK_DISTANCE = 25 @@ -200,7 +201,7 @@ EXIT_EVENTS_LOOKBACK_WINDOW_IN_SLOTS = 14 * 7200 # 14 days # OracleReportSanityChecker -ORACLE_REPORT_SANITY_CHECKER = "0x6232397ebac4f5772e53285B26c47914E9461E75" +ORACLE_REPORT_SANITY_CHECKER = "0xf1647c86E6D7959f638DD9CE1d90e2F3C9503129" APPEARED_VALIDATORS_PER_DAY_LIMIT = 1800 EXITED_VALIDATORS_PER_DAY_LIMIT = 3600 ANNUAL_BALANCE_INCREASE_BP_LIMIT = 1000 # 10% @@ -215,7 +216,7 @@ CL_BALANCE_ORACLES_ERROR_UPPER_BP_LIMIT = 50 # Burner -BURNER = "0xD15a672319Cf0352560eE76d9e89eAB0889046D3" +BURNER = "0xE76c52750019b80B43E36DF30bf4060EB73F573a" TOTAL_NON_COVER_SHARES_BURNT = 32145684728326685744 TOTAL_COVER_SHARES_BURNT = 0 @@ -238,11 +239,11 @@ # AccountingOracle # and its corresponding HashConsensus ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" -ACCOUNTING_ORACLE_IMPL = "0xE9906E543274cebcd335d2C560094089e9547e8d" +ACCOUNTING_ORACLE_IMPL = "0x1455B96780A93e08abFE41243Db92E2fCbb0141c" HASH_CONSENSUS_FOR_AO = "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288" AO_EPOCHS_PER_FRAME = 225 AO_FAST_LANE_LENGTH_SLOTS = 100 -AO_CONSENSUS_VERSION = 4 +AO_CONSENSUS_VERSION = 5 # ValidatorsExitBusOracle VALIDATORS_EXIT_BUS_ORACLE = "0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e" @@ -363,7 +364,7 @@ OBOL_LIDO_SPLIT_IMPL = "0x2fB59065F049e0D0E3180C6312FA0FeB5Bbf0FE3" # Ethereum Part for Optimism bridge -L1_TOKEN_RATE_NOTIFIER = "0xe6793B9e4FbA7DE0ee833F9D02bba7DB5EB27823" +L1_TOKEN_RATE_NOTIFIER = "0x25e35855783bec3E49355a29e110f02Ed8b05ba9" L1_OPTIMISM_TOKEN_RATE_PUSHER = "0xd54c1c6413caac3477AC14b2a80D5398E3c32FfE" L1_OPTIMISM_TOKENS_BRIDGE = "0x76943C0D61395d8F2edF9060e1533529cAe05dE6" L1_OPTIMISM_TOKENS_BRIDGE_IMPL = "0x29C5c51A031165CE62F964966A6399b81165EFA4" @@ -521,3 +522,16 @@ "WITHDRAWAL_BLOCKERS": (WITHDRAWAL_QUEUE, VALIDATORS_EXIT_BUS_ORACLE, TRIGGERABLE_WITHDRAWALS_GATEWAY), } } + +# Lido V3 - stVaults +VAULT_HUB = "0x1d201BE093d847f6446530Efb0E8Fb426d176709" +ACCOUNTING = "0x23ED611be0e1a820978875C0122F92260804cdDf" +ACCOUNTING_IMPL = "0xd43a3E984071F40d5d840f60708Af0e9526785df" +OPERATOR_GRID = "0xC69685E89Cefc327b43B7234AC646451B27c544d" +LAZY_ORACLE = "0x5DB427080200c235F2Ae8Cd17A7be87921f7AD6c" +PREDEPOSIT_GUARANTEE = "0xF4bF42c6D6A0E38825785048124DBAD6c9eaaac3" +VAULTS_ADAPTER = "0xe2DE6d2DefF15588a71849c0429101F8ca9FB14D" +GATE_SEAL_V3 = "0x881dAd714679A6FeaA636446A0499101375A365c" +STAKING_VAULT_BEACON = "0x5FbE8cEf9CCc56ad245736D3C5bAf82ad54Ca789" + +INITIAL_MAX_EXTERNAL_RATIO_BP = 300 # 3% diff --git a/interfaces/Accounting.json b/interfaces/Accounting.json new file mode 100644 index 000000000..95eb78cc6 --- /dev/null +++ b/interfaces/Accounting.json @@ -0,0 +1,327 @@ +[ + { + "inputs": [ + { + "internalType": "contract ILidoLocator", + "name": "_lidoLocator", + "type": "address" + }, + { + "internalType": "contract ILido", + "name": "_lido", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "reportTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "upperBoundTimestamp", + "type": "uint256" + } + ], + "name": "IncorrectReportTimestamp", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "reportValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxValidators", + "type": "uint256" + } + ], + "name": "IncorrectReportValidators", + "type": "error" + }, + { + "inputs": [], + "name": "InternalSharesCantBeZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "operation", + "type": "string" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "NotAuthorized", + "type": "error" + }, + { + "inputs": [], + "name": "LIDO", + "outputs": [ + { + "internalType": "contract ILido", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timeElapsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "clValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "clBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawalVaultBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "elRewardsVaultBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesRequestedToBurn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "withdrawalFinalizationBatches", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "simulatedShareRate", + "type": "uint256" + } + ], + "internalType": "struct ReportValues", + "name": "_report", + "type": "tuple" + } + ], + "name": "handleOracleReport", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timeElapsed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "clValidators", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "clBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawalVaultBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "elRewardsVaultBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesRequestedToBurn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "withdrawalFinalizationBatches", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "simulatedShareRate", + "type": "uint256" + } + ], + "internalType": "struct ReportValues", + "name": "_report", + "type": "tuple" + } + ], + "name": "simulateOracleReport", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "withdrawalsVaultTransfer", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "elRewardsVaultTransfer", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "etherToFinalizeWQ", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesToFinalizeWQ", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesToBurnForWithdrawals", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalSharesToBurn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sharesToMintAsFees", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address[]", + "name": "moduleFeeRecipients", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "moduleIds", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "moduleSharesToMint", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "treasurySharesToMint", + "type": "uint256" + } + ], + "internalType": "struct Accounting.FeeDistribution", + "name": "feeDistribution", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "principalClBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preTotalShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preTotalPooledEther", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postInternalShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postInternalEther", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postTotalShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "postTotalPooledEther", + "type": "uint256" + } + ], + "internalType": "struct Accounting.CalculatedValues", + "name": "update", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } + ] diff --git a/interfaces/AccountingOracle.json b/interfaces/AccountingOracle.json index 35c6e7e9f..73e7ef341 100644 --- a/interfaces/AccountingOracle.json +++ b/interfaces/AccountingOracle.json @@ -6,16 +6,6 @@ "name": "lidoLocator", "type": "address" }, - { - "internalType": "address", - "name": "lido", - "type": "address" - }, - { - "internalType": "address", - "name": "legacyOracle", - "type": "address" - }, { "internalType": "uint256", "name": "secondsPerSlot", @@ -145,11 +135,6 @@ "name": "InvalidExtraDataSortOrder", "type": "error" }, - { - "inputs": [], - "name": "LegacyOracleCannotBeZero", - "type": "error" - }, { "inputs": [], "name": "LidoCannotBeZero", @@ -728,32 +713,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "LEGACY_ORACLE", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "LIDO", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "LOCATOR", @@ -840,14 +799,7 @@ "type": "uint256" } ], - "name": "finalizeUpgrade_v2", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "finalizeUpgrade_v3", + "name": "finalizeUpgrade_v4", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1096,29 +1048,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "admin", - "type": "address" - }, - { - "internalType": "address", - "name": "consensusContract", - "type": "address" - }, - { - "internalType": "uint256", - "name": "consensusVersion", - "type": "uint256" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -1142,7 +1071,7 @@ "type": "uint256" } ], - "name": "initializeWithoutMigration", + "name": "initialize", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -1296,6 +1225,16 @@ "name": "isBunkerMode", "type": "bool" }, + { + "internalType": "bytes32", + "name": "vaultsDataTreeRoot", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "vaultsDataTreeCid", + "type": "string" + }, { "internalType": "uint256", "name": "extraDataFormat", diff --git a/interfaces/Burner.json b/interfaces/Burner.json index ac643f234..edaffbaa9 100644 --- a/interfaces/Burner.json +++ b/interfaces/Burner.json @@ -1,24 +1,25 @@ [ { "inputs": [ - { "internalType": "address", "name": "_admin", "type": "address" }, - { "internalType": "address", "name": "_treasury", "type": "address" }, - { "internalType": "address", "name": "_stETH", "type": "address" }, { - "internalType": "uint256", - "name": "_totalCoverSharesBurnt", - "type": "uint256" + "internalType": "address", + "name": "_locator", + "type": "address" }, { - "internalType": "uint256", - "name": "_totalNonCoverSharesBurnt", - "type": "uint256" + "internalType": "address", + "name": "_stETH", + "type": "address" } ], "stateMutability": "nonpayable", "type": "constructor" }, - { "inputs": [], "name": "AppAuthLidoFailed", "type": "error" }, + { + "inputs": [], + "name": "AppAuthFailed", + "type": "error" + }, { "inputs": [ { @@ -26,20 +27,100 @@ "name": "requestedAmount", "type": "uint256" }, - { "internalType": "uint256", "name": "actualAmount", "type": "uint256" } + { + "internalType": "uint256", + "name": "actualAmount", + "type": "uint256" + } ], "name": "BurnAmountExceedsActual", "type": "error" }, - { "inputs": [], "name": "DirectETHTransfer", "type": "error" }, - { "inputs": [], "name": "StETHRecoveryWrongFunc", "type": "error" }, { - "inputs": [{ "internalType": "string", "name": "field", "type": "string" }], + "inputs": [], + "name": "DirectETHTransfer", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidContractVersionIncrement", + "type": "error" + }, + { + "inputs": [], + "name": "MigrationNotAllowedOrAlreadyMigrated", + "type": "error" + }, + { + "inputs": [], + "name": "NonZeroContractVersionOnInit", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyLidoCanMigrate", + "type": "error" + }, + { + "inputs": [], + "name": "StETHRecoveryWrongFunc", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + } + ], + "name": "UnexpectedContractVersion", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "field", + "type": "string" + } + ], "name": "ZeroAddress", "type": "error" }, - { "inputs": [], "name": "ZeroBurnAmount", "type": "error" }, - { "inputs": [], "name": "ZeroRecoveryAmount", "type": "error" }, + { + "inputs": [], + "name": "ZeroBurnAmount", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroRecoveryAmount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "version", + "type": "uint256" + } + ], + "name": "ContractVersionSet", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -249,93 +330,192 @@ { "inputs": [], "name": "DEFAULT_ADMIN_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "REQUEST_BURN_MY_STETH_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "LIDO", + "outputs": [ + { + "internalType": "contract ILido", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "REQUEST_BURN_SHARES_ROLE", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "STETH", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "REQUEST_BURN_MY_STETH_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], - "name": "TREASURY", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "REQUEST_BURN_SHARES_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "uint256", "name": "_sharesToBurn", "type": "uint256" } + { + "internalType": "uint256", + "name": "_sharesToBurn", + "type": "uint256" + } ], "name": "commitSharesToBurn", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "getContractVersion", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getCoverSharesBurnt", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getExcessStETH", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getNonCoverSharesBurnt", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } ], "name": "getRoleAdmin", - "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "uint256", "name": "index", "type": "uint256" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } ], "name": "getRoleMember", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } ], "name": "getRoleMemberCount", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, @@ -343,16 +523,32 @@ "inputs": [], "name": "getSharesRequestedToBurn", "outputs": [ - { "internalType": "uint256", "name": "coverShares", "type": "uint256" }, - { "internalType": "uint256", "name": "nonCoverShares", "type": "uint256" } + { + "internalType": "uint256", + "name": "coverShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonCoverShares", + "type": "uint256" + } ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "grantRole", "outputs": [], @@ -361,18 +557,84 @@ }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "hasRole", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "uint256", "name": "_amount", "type": "uint256" } + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isMigrationAllowed", + "type": "bool" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isMigrationAllowed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_oldBurner", + "type": "address" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } ], "name": "recoverERC20", "outputs": [], @@ -381,8 +643,16 @@ }, { "inputs": [ - { "internalType": "address", "name": "_token", "type": "address" }, - { "internalType": "uint256", "name": "_tokenId", "type": "uint256" } + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + } ], "name": "recoverERC721", "outputs": [], @@ -398,14 +668,35 @@ }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "renounceRole", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_sharesAmountToBurn", + "type": "uint256" + } + ], + "name": "requestBurnMyShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -434,7 +725,11 @@ }, { "inputs": [ - { "internalType": "address", "name": "_from", "type": "address" }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, { "internalType": "uint256", "name": "_sharesAmountToBurn", @@ -448,7 +743,11 @@ }, { "inputs": [ - { "internalType": "address", "name": "_from", "type": "address" }, + { + "internalType": "address", + "name": "_from", + "type": "address" + }, { "internalType": "uint256", "name": "_sharesAmountToBurn", @@ -462,8 +761,16 @@ }, { "inputs": [ - { "internalType": "bytes32", "name": "role", "type": "bytes32" }, - { "internalType": "address", "name": "account", "type": "address" } + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } ], "name": "revokeRole", "outputs": [], @@ -472,12 +779,25 @@ }, { "inputs": [ - { "internalType": "bytes4", "name": "interfaceId", "type": "bytes4" } + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } ], "name": "supportsInterface", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, - { "stateMutability": "payable", "type": "receive" } + { + "stateMutability": "payable", + "type": "receive" + } ] diff --git a/interfaces/IVaultsAdapter.json b/interfaces/IVaultsAdapter.json new file mode 100644 index 000000000..6e85089d7 --- /dev/null +++ b/interfaces/IVaultsAdapter.json @@ -0,0 +1,89 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_pubkeys", + "type": "bytes" + } + ], + "name": "forceValidatorExit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isInJail", + "type": "bool" + } + ], + "name": "setVaultJailStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_debtVault", + "type": "address" + }, + { + "internalType": "address", + "name": "_acceptorVault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shares", + "type": "uint256" + } + ], + "name": "socializeBadDebt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_infrastructureFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reservationFeeBP", + "type": "uint256" + } + ], + "name": "updateVaultFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/interfaces/LazyOracle.json b/interfaces/LazyOracle.json new file mode 100644 index 000000000..8e39d00b9 --- /dev/null +++ b/interfaces/LazyOracle.json @@ -0,0 +1,1144 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_lidoLocator", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "AdminCannotBeZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "feeIncrease", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeeIncrease", + "type": "uint256" + } + ], + "name": "CumulativeLidoFeesTooLarge", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "reportingFees", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "previousFees", + "type": "uint256" + } + ], + "name": "CumulativeLidoFeesTooLow", + "type": "error" + }, + { + "inputs": [], + "name": "InOutDeltaCacheIsOverwritten", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMaxLiabilityShares", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProof", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "feeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeeRate", + "type": "uint256" + } + ], + "name": "MaxLidoFeeRatePerSecondTooLarge", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "rewardRatio", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxRewardRatio", + "type": "uint256" + } + ], + "name": "MaxRewardRatioTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "NotAuthorized", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "quarantinePeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxQuarantinePeriod", + "type": "uint256" + } + ], + "name": "QuarantinePeriodTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "TotalValueTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "UnderflowInTotalValueCalculation", + "type": "error" + }, + { + "inputs": [], + "name": "VaultReportIsFreshEnough", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "delta", + "type": "uint256" + } + ], + "name": "QuarantineActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "delta", + "type": "uint256" + } + ], + "name": "QuarantineReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "QuarantineRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "totalValueReminder", + "type": "uint256" + } + ], + "name": "QuarantineUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "quarantinePeriod", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxRewardRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "maxLidoFeeRatePerSecond", + "type": "uint256" + } + ], + "name": "SanityParamsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "root", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "cid", + "type": "string" + } + ], + "name": "VaultsReportDataUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_LIDO_FEE_RATE_PER_SECOND", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_QUARANTINE_PERIOD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_REWARD_RATIO", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPDATE_SANITY_PARAMS_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "_pubkeys", + "type": "bytes[]" + } + ], + "name": "batchValidatorStatuses", + "outputs": [ + { + "components": [ + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + }, + { + "internalType": "contract IStakingVault", + "name": "stakingVault", + "type": "address" + }, + { + "internalType": "address", + "name": "nodeOperator", + "type": "address" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorStatus[]", + "name": "batch", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + } + ], + "name": "batchVaultsInfo", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "aggregatedBalance", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "inOutDelta", + "type": "int256" + }, + { + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "liabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mintableStETH", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint16", + "name": "reserveRatioBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "forcedRebalanceThresholdBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "infraFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "reservationFeeBP", + "type": "uint16" + }, + { + "internalType": "bool", + "name": "pendingDisconnect", + "type": "bool" + } + ], + "internalType": "struct LazyOracle.VaultInfo[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMembers", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_quarantinePeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxRewardRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxLidoFeeRatePerSecond", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "latestReportData", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "treeRoot", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "reportCid", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestReportTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLidoFeeRatePerSecond", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxRewardRatioBP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quarantinePeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "quarantineValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "removeVaultQuarantine", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_vaultsDataTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_vaultsDataRefSlot", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_vaultsDataTreeRoot", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "_vaultsDataReportCid", + "type": "string" + } + ], + "name": "updateReportData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_quarantinePeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxRewardRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxLidoFeeRatePerSecond", + "type": "uint256" + } + ], + "name": "updateSanityParams", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_totalValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_cumulativeLidoFees", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_slashingReserve", + "type": "uint256" + }, + { + "internalType": "bytes32[]", + "name": "_proof", + "type": "bytes32[]" + } + ], + "name": "updateVaultData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultInfo", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "aggregatedBalance", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "inOutDelta", + "type": "int256" + }, + { + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "liabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mintableStETH", + "type": "uint256" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint16", + "name": "reserveRatioBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "forcedRebalanceThresholdBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "infraFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "reservationFeeBP", + "type": "uint16" + }, + { + "internalType": "bool", + "name": "pendingDisconnect", + "type": "bool" + } + ], + "internalType": "struct LazyOracle.VaultInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultQuarantine", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pendingTotalValueIncrease", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "endTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalValueRemainder", + "type": "uint256" + } + ], + "internalType": "struct LazyOracle.QuarantineInfo", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/Lido.json b/interfaces/Lido.json index c28e25b8b..e62344e9b 100644 --- a/interfaces/Lido.json +++ b/interfaces/Lido.json @@ -1 +1,1926 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_CONTROL_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_maxStakeLimit","type":"uint256"},{"name":"_stakeLimitIncreasePerBlock","type":"uint256"}],"name":"setStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"finalizeUpgrade_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_newDepositedValidators","type":"uint256"}],"name":"unsafeChangeDepositedValidators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lidoLocator","type":"address"},{"name":"_eip712StETH","type":"address"}],"name":"initialize","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"receiveELRewards","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentStakeLimit","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getStakeLimitFullInfo","outputs":[{"name":"isStakingPaused","type":"bool"},{"name":"isStakingLimitSet","type":"bool"},{"name":"currentStakeLimit","type":"uint256"},{"name":"maxStakeLimit","type":"uint256"},{"name":"maxStakeLimitGrowthBlocks","type":"uint256"},{"name":"prevStakeLimit","type":"uint256"},{"name":"prevStakeBlockNumber","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferSharesFrom","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"receiveWithdrawals","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"nonces","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"eip712Domain","outputs":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getContractVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getEIP712StETH","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDepositsCount","type":"uint256"},{"name":"_stakingModuleId","type":"uint256"},{"name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"removeStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_reportTimestamp","type":"uint256"},{"name":"_timeElapsed","type":"uint256"},{"name":"_clValidators","type":"uint256"},{"name":"_clBalance","type":"uint256"},{"name":"_withdrawalVaultBalance","type":"uint256"},{"name":"_elRewardsVaultBalance","type":"uint256"},{"name":"_sharesRequestedToBurn","type":"uint256"},{"name":"_withdrawalFinalizationBatches","type":"uint256[]"},{"name":"_simulatedShareRate","type":"uint256"}],"name":"handleOracleReport","outputs":[{"name":"postRebaseAmounts","type":"uint256[4]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"totalFee","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_deadline","type":"uint256"},{"name":"_v","type":"uint8"},{"name":"_r","type":"bytes32"},{"name":"_s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLidoLocator","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"canDeposit","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDepositableEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalELRewardsCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"maxStakeLimit","type":"uint256"},{"indexed":false,"name":"stakeLimitIncreasePerBlock","type":"uint256"}],"name":"StakingLimitSet","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingLimitRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLValidators","type":"uint256"},{"indexed":false,"name":"postCLValidators","type":"uint256"}],"name":"CLValidatorsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"depositedValidators","type":"uint256"}],"name":"DepositedValidatorsChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"preCLBalance","type":"uint256"},{"indexed":false,"name":"postCLBalance","type":"uint256"},{"indexed":false,"name":"withdrawalsWithdrawn","type":"uint256"},{"indexed":false,"name":"executionLayerRewardsWithdrawn","type":"uint256"},{"indexed":false,"name":"postBufferedEther","type":"uint256"}],"name":"ETHDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"reportTimestamp","type":"uint256"},{"indexed":false,"name":"timeElapsed","type":"uint256"},{"indexed":false,"name":"preTotalShares","type":"uint256"},{"indexed":false,"name":"preTotalEther","type":"uint256"},{"indexed":false,"name":"postTotalShares","type":"uint256"},{"indexed":false,"name":"postTotalEther","type":"uint256"},{"indexed":false,"name":"sharesMintedAsFees","type":"uint256"}],"name":"TokenRebased","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"lidoLocator","type":"address"}],"name":"LidoLocatorSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"ELRewardsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"WithdrawalsReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"eip712StETH","type":"address"}],"name":"EIP712StETHInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"preRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"postRebaseTokenAmount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"}] \ No newline at end of file +[ + { + "constant": true, + "inputs": [ + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "getPooledEthBySharesRoundUp", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resume", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "mintExternalShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "stop", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "hasInitialized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKING_CONTROL_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_ethAmount", + "type": "uint256" + } + ], + "name": "getSharesByPooledEth", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_timeElapsed", + "type": "uint256" + }, + { + "name": "_preTotalShares", + "type": "uint256" + }, + { + "name": "_preTotalEther", + "type": "uint256" + }, + { + "name": "_postTotalShares", + "type": "uint256" + }, + { + "name": "_postTotalEther", + "type": "uint256" + }, + { + "name": "_postInternalShares", + "type": "uint256" + }, + { + "name": "_postInternalEther", + "type": "uint256" + }, + { + "name": "_sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "emitTokenRebase", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isStakingPaused", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_script", + "type": "bytes" + } + ], + "name": "getEVMScriptExecutor", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxStakeLimit", + "type": "uint256" + }, + { + "name": "_stakeLimitIncreasePerBlock", + "type": "uint256" + } + ], + "name": "setStakingLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getRecoveryVault", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalPooledEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newDepositedValidators", + "type": "uint256" + } + ], + "name": "unsafeChangeDepositedValidators", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTreasury", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isStopped", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBufferedEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_lidoLocator", + "type": "address" + }, + { + "name": "_eip712StETH", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "receiveELRewards", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "mintShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getWithdrawalCredentials", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_preClValidators", + "type": "uint256" + }, + { + "name": "_reportClValidators", + "type": "uint256" + }, + { + "name": "_reportClBalance", + "type": "uint256" + } + ], + "name": "processClStateUpdate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentStakeLimit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getExternalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "internalizeExternalBadDebt", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getStakeLimitFullInfo", + "outputs": [ + { + "name": "isStakingPaused_", + "type": "bool" + }, + { + "name": "isStakingLimitSet", + "type": "bool" + }, + { + "name": "currentStakeLimit", + "type": "uint256" + }, + { + "name": "maxStakeLimit", + "type": "uint256" + }, + { + "name": "maxStakeLimitGrowthBlocks", + "type": "uint256" + }, + { + "name": "prevStakeLimit", + "type": "uint256" + }, + { + "name": "prevStakeBlockNumber", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "transferSharesFrom", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "burnExternalShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "resumeStaking", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFeeDistribution", + "outputs": [ + { + "name": "treasuryFeeBasisPoints", + "type": "uint16" + }, + { + "name": "insuranceFeeBasisPoints", + "type": "uint16" + }, + { + "name": "operatorsFeeBasisPoints", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "receiveWithdrawals", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "getPooledEthByShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "rebalanceExternalEtherToInternal", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "token", + "type": "address" + } + ], + "name": "allowRecoverability", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "appId", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "burnShares", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_oldBurner", + "type": "address" + }, + { + "name": "_contractsWithBurnerAllowances", + "type": "address[]" + }, + { + "name": "_initialMaxExternalRatioBP", + "type": "uint256" + } + ], + "name": "finalizeUpgrade_v3", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getMaxMintableExternalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getContractVersion", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getInitializationBlock", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_sharesAmount", + "type": "uint256" + } + ], + "name": "transferShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "name": "_reportClBalance", + "type": "uint256" + }, + { + "name": "_principalCLBalance", + "type": "uint256" + }, + { + "name": "_withdrawalsToWithdraw", + "type": "uint256" + }, + { + "name": "_elRewardsToWithdraw", + "type": "uint256" + }, + { + "name": "_lastWithdrawalRequestToFinalize", + "type": "uint256" + }, + { + "name": "_withdrawalsShareRate", + "type": "uint256" + }, + { + "name": "_etherToLockOnWithdrawalQueue", + "type": "uint256" + } + ], + "name": "collectRewardsAndProcessWithdrawals", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEIP712StETH", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getMaxExternalRatioBP", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "transferToVault", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_sender", + "type": "address" + }, + { + "name": "_role", + "type": "bytes32" + }, + { + "name": "_params", + "type": "uint256[]" + } + ], + "name": "canPerform", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_referral", + "type": "address" + } + ], + "name": "submit", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getEVMScriptRegistry", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_recipient", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxDepositsCount", + "type": "uint256" + }, + { + "name": "_stakingModuleId", + "type": "uint256" + }, + { + "name": "_depositCalldata", + "type": "bytes" + } + ], + "name": "deposit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getBeaconStat", + "outputs": [ + { + "name": "depositedValidators", + "type": "uint256" + }, + { + "name": "beaconValidators", + "type": "uint256" + }, + { + "name": "beaconBalance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "removeStakingLimit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getFee", + "outputs": [ + { + "name": "totalFee", + "type": "uint16" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kernel", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalShares", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_deadline", + "type": "uint256" + }, + { + "name": "_v", + "type": "uint8" + }, + { + "name": "_r", + "type": "bytes32" + }, + { + "name": "_s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isPetrified", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getExternalEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getLidoLocator", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "canDeposit", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "STAKING_PAUSE_ROLE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getDepositableEther", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_maxExternalRatioBP", + "type": "uint256" + } + ], + "name": "setMaxExternalRatioBP", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_account", + "type": "address" + } + ], + "name": "sharesOf", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "pauseStaking", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getTotalELRewardsCollected", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingResumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "maxStakeLimit", + "type": "uint256" + }, + { + "indexed": false, + "name": "stakeLimitIncreasePerBlock", + "type": "uint256" + } + ], + "name": "StakingLimitSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "StakingLimitRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "preCLValidators", + "type": "uint256" + }, + { + "indexed": false, + "name": "postCLValidators", + "type": "uint256" + } + ], + "name": "CLValidatorsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "depositedValidators", + "type": "uint256" + } + ], + "name": "DepositedValidatorsChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "preCLBalance", + "type": "uint256" + }, + { + "indexed": false, + "name": "postCLBalance", + "type": "uint256" + }, + { + "indexed": false, + "name": "withdrawalsWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "name": "executionLayerRewardsWithdrawn", + "type": "uint256" + }, + { + "indexed": false, + "name": "postBufferedEther", + "type": "uint256" + } + ], + "name": "ETHDistributed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "timeElapsed", + "type": "uint256" + }, + { + "indexed": false, + "name": "preTotalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "preTotalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "postTotalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "postTotalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "TokenRebased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "lidoLocator", + "type": "address" + } + ], + "name": "LidoLocatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "ELRewardsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawalsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "name": "referral", + "type": "address" + } + ], + "name": "Submitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Unbuffered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "name": "postInternalShares", + "type": "uint256" + }, + { + "indexed": false, + "name": "postInternalEther", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesMintedAsFees", + "type": "uint256" + } + ], + "name": "InternalShareRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "receiver", + "type": "address" + }, + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalSharesMinted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalSharesBurnt", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "maxExternalRatioBP", + "type": "uint256" + } + ], + "name": "MaxExternalRatioBPSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "ExternalEtherTransferredToBuffer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "ExternalBadDebtInternalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "executor", + "type": "address" + }, + { + "indexed": false, + "name": "script", + "type": "bytes" + }, + { + "indexed": false, + "name": "input", + "type": "bytes" + }, + { + "indexed": false, + "name": "returnData", + "type": "bytes" + } + ], + "name": "ScriptResult", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "name": "token", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "RecoverToVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "eip712StETH", + "type": "address" + } + ], + "name": "EIP712StETHInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "sharesValue", + "type": "uint256" + } + ], + "name": "TransferShares", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "account", + "type": "address" + }, + { + "indexed": false, + "name": "preRebaseTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "postRebaseTokenAmount", + "type": "uint256" + }, + { + "indexed": false, + "name": "sharesAmount", + "type": "uint256" + } + ], + "name": "SharesBurnt", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Stopped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "version", + "type": "uint256" + } + ], + "name": "ContractVersionSet", + "type": "event" + } +] diff --git a/interfaces/LidoLocator.json b/interfaces/LidoLocator.json index 5bb321206..3c480986e 100644 --- a/interfaces/LidoLocator.json +++ b/interfaces/LidoLocator.json @@ -1,2 +1,497 @@ -[{"inputs":[{"components":[{"internalType":"address","name":"accountingOracle","type":"address"},{"internalType":"address","name":"depositSecurityModule","type":"address"},{"internalType":"address","name":"elRewardsVault","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"oracleReportSanityChecker","type":"address"},{"internalType":"address","name":"postTokenRebaseReceiver","type":"address"},{"internalType":"address","name":"burner","type":"address"},{"internalType":"address","name":"stakingRouter","type":"address"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"address","name":"validatorsExitBusOracle","type":"address"},{"internalType":"address","name":"withdrawalQueue","type":"address"},{"internalType":"address","name":"withdrawalVault","type":"address"},{"internalType":"address","name":"oracleDaemonConfig","type":"address"},{"internalType":"address","name":"validatorExitDelayVerifier","type":"address"},{"internalType":"address","name":"triggerableWithdrawalsGateway","type":"address"}],"internalType":"struct LidoLocator.Config","name":"_config","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"inputs":[],"name":"accountingOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"coreComponents","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositSecurityModule","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"elRewardsVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"legacyOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleDaemonConfig","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleReportComponentsForLido","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleReportSanityChecker","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"postTokenRebaseReceiver","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"triggerableWithdrawalsGateway","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"validatorExitDelayVerifier","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"validatorsExitBusOracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalQueue","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawalVault","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] - +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "accountingOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "depositSecurityModule", + "type": "address" + }, + { + "internalType": "address", + "name": "elRewardsVault", + "type": "address" + }, + { + "internalType": "address", + "name": "lido", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleReportSanityChecker", + "type": "address" + }, + { + "internalType": "address", + "name": "postTokenRebaseReceiver", + "type": "address" + }, + { + "internalType": "address", + "name": "burner", + "type": "address" + }, + { + "internalType": "address", + "name": "stakingRouter", + "type": "address" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "validatorsExitBusOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalQueue", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalVault", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleDaemonConfig", + "type": "address" + }, + { + "internalType": "address", + "name": "validatorExitDelayVerifier", + "type": "address" + }, + { + "internalType": "address", + "name": "triggerableWithdrawalsGateway", + "type": "address" + }, + { + "internalType": "address", + "name": "accounting", + "type": "address" + }, + { + "internalType": "address", + "name": "predepositGuarantee", + "type": "address" + }, + { + "internalType": "address", + "name": "wstETH", + "type": "address" + }, + { + "internalType": "address", + "name": "vaultHub", + "type": "address" + }, + { + "internalType": "address", + "name": "vaultFactory", + "type": "address" + }, + { + "internalType": "address", + "name": "lazyOracle", + "type": "address" + }, + { + "internalType": "address", + "name": "operatorGrid", + "type": "address" + } + ], + "internalType": "struct LidoLocator.Config", + "name": "_config", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "accounting", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "accountingOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "burner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "coreComponents", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "depositSecurityModule", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "elRewardsVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lazyOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lido", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operatorGrid", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleDaemonConfig", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleReportComponents", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "oracleReportSanityChecker", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "postTokenRebaseReceiver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "predepositGuarantee", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stakingRouter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "triggerableWithdrawalsGateway", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "validatorExitDelayVerifier", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "validatorsExitBusOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultFactory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultHub", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalQueue", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "wstETH", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/OperatorGrid.json b/interfaces/OperatorGrid.json new file mode 100644 index 000000000..ddda111b1 --- /dev/null +++ b/interfaces/OperatorGrid.json @@ -0,0 +1,1582 @@ +[ + { + "inputs": [ + { + "internalType": "contract ILidoLocator", + "name": "_locator", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ArrayLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "CannotChangeToDefaultTier", + "type": "error" + }, + { + "inputs": [], + "name": "ConfirmExpiryOutOfBounds", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + } + ], + "name": "ForcedRebalanceThresholdTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "GroupExists", + "type": "error" + }, + { + "inputs": [], + "name": "GroupLimitExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "GroupNotExists", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxInfraFeeBP", + "type": "uint256" + } + ], + "name": "InfraFeeTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "valueBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxValueBP", + "type": "uint256" + } + ], + "name": "InvalidBasisPoints", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLiquidityFeeBP", + "type": "uint256" + } + ], + "name": "LiquidityFeeTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "NodeOperatorNotExists", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "operation", + "type": "string" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "NotAuthorized", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestedShareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tierShareLimit", + "type": "uint256" + } + ], + "name": "RequestedShareLimitTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestedSHareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vaultShares", + "type": "uint256" + } + ], + "name": "RequestedShareLimitTooLow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxReservationFeeBP", + "type": "uint256" + } + ], + "name": "ReservationFeeTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxReserveRatioBP", + "type": "uint256" + } + ], + "name": "ReserveRatioTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [], + "name": "SenderNotMember", + "type": "error" + }, + { + "inputs": [], + "name": "ShareLimitAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "TierAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "TierLimitExceeded", + "type": "error" + }, + { + "inputs": [], + "name": "TierNotExists", + "type": "error" + }, + { + "inputs": [], + "name": "TierNotInOperatorGroup", + "type": "error" + }, + { + "inputs": [], + "name": "VaultAlreadySyncedWithTier", + "type": "error" + }, + { + "inputs": [], + "name": "VaultInJail", + "type": "error" + }, + { + "inputs": [], + "name": "VaultInJailAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "VaultNotConnected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "argument", + "type": "string" + } + ], + "name": "ZeroArgument", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroConfirmingRoles", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldConfirmExpiry", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newConfirmExpiry", + "type": "uint256" + } + ], + "name": "ConfirmExpirySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + } + ], + "name": "GroupAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + } + ], + "name": "GroupShareLimitUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "member", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "roleOrAddress", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "confirmTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expiryTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "RoleMemberConfirmed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "name": "TierAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + } + ], + "name": "TierChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "name": "TierUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isInJail", + "type": "bool" + } + ], + "name": "VaultJailStatusUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_TIER_ID", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_TIER_OPERATOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_CONFIRM_EXPIRY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_CONFIRM_EXPIRY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REGISTRY_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "_tierIds", + "type": "uint256[]" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "internalType": "struct TierParams[]", + "name": "_tierParams", + "type": "tuple[]" + } + ], + "name": "alterTiers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_requestedTierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_requestedShareLimit", + "type": "uint256" + } + ], + "name": "changeTier", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_callData", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "_role", + "type": "bytes32" + } + ], + "name": "confirmation", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "effectiveShareLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConfirmExpiry", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMembers", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "group", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "liabilityShares", + "type": "uint96" + }, + { + "internalType": "uint256[]", + "name": "tierIds", + "type": "uint256[]" + } + ], + "internalType": "struct OperatorGrid.Group", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "internalType": "struct TierParams", + "name": "_defaultTierParams", + "type": "tuple" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isVaultInJail", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "nodeOperatorAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nodeOperatorCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "onBurnedShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "_overrideLimits", + "type": "bool" + } + ], + "name": "onMintedShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shareLimit", + "type": "uint256" + } + ], + "name": "registerGroup", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "internalType": "struct TierParams[]", + "name": "_tiers", + "type": "tuple[]" + } + ], + "name": "registerTiers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "resetVaultTier", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newConfirmExpiry", + "type": "uint256" + } + ], + "name": "setConfirmExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bool", + "name": "_isInJail", + "type": "bool" + } + ], + "name": "setVaultJailStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "syncTier", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_tierId", + "type": "uint256" + } + ], + "name": "tier", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "liabilityShares", + "type": "uint96" + }, + { + "internalType": "uint16", + "name": "reserveRatioBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "forcedRebalanceThresholdBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "infraFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "reservationFeeBP", + "type": "uint16" + } + ], + "internalType": "struct OperatorGrid.Tier", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tiersCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shareLimit", + "type": "uint256" + } + ], + "name": "updateGroupShareLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reservationFeeBP", + "type": "uint256" + } + ], + "name": "updateVaultFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_requestedShareLimit", + "type": "uint256" + } + ], + "name": "updateVaultShareLimit", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultTierInfo", + "outputs": [ + { + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tierId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/PSMVariant1Actions.json b/interfaces/PSMVariant1Actions.json new file mode 100644 index 000000000..1d5a50792 --- /dev/null +++ b/interfaces/PSMVariant1Actions.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_psm","type":"address"},{"internalType":"address","name":"_savingsToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"dai","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gem","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"psm","outputs":[{"internalType":"contract PSMVariant1Like","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"redeemAndSwap","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"savingsToken","outputs":[{"internalType":"contract IERC4626","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"swapAndDeposit","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"maxAmountIn","type":"uint256"}],"name":"withdrawAndSwap","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/interfaces/PredepositGuarantee.json b/interfaces/PredepositGuarantee.json new file mode 100644 index 000000000..ee708d803 --- /dev/null +++ b/interfaces/PredepositGuarantee.json @@ -0,0 +1,2001 @@ +[ + { + "inputs": [ + { + "internalType": "bytes4", + "name": "_genesisForkVersion", + "type": "bytes4" + }, + { + "internalType": "GIndex", + "name": "_gIFirstValidator", + "type": "bytes32" + }, + { + "internalType": "GIndex", + "name": "_gIFirstValidatorAfterChange", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "_pivotSlot", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "ArrayLengthsNotMatch", + "type": "error" + }, + { + "inputs": [], + "name": "CompensateFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyDeposits", + "type": "error" + }, + { + "inputs": [], + "name": "IndexOutOfRange", + "type": "error" + }, + { + "inputs": [], + "name": "InputHasInfinityPoints", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDepositYLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPubkeyLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSlot", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "InvalidTopUpAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "InvalidValidatorStage", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "locked", + "type": "uint256" + } + ], + "name": "LockedIsNotZero", + "type": "error" + }, + { + "inputs": [], + "name": "NotDepositor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "unlocked", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "NotEnoughUnlocked", + "type": "error" + }, + { + "inputs": [], + "name": "NotGuarantor", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "NotStakingVaultOwner", + "type": "error" + }, + { + "inputs": [], + "name": "NothingToRefund", + "type": "error" + }, + { + "inputs": [], + "name": "PauseUntilMustBeInFuture", + "type": "error" + }, + { + "inputs": [], + "name": "PausedExpected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "depositAmount", + "type": "uint256" + } + ], + "name": "PredepositAmountInvalid", + "type": "error" + }, + { + "inputs": [], + "name": "RefundFailed", + "type": "error" + }, + { + "inputs": [], + "name": "ResumedExpected", + "type": "error" + }, + { + "inputs": [], + "name": "RootNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "SameDepositor", + "type": "error" + }, + { + "inputs": [], + "name": "SameGuarantor", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "ValidatorNotActivated", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + } + ], + "name": "ValidatorNotEligibleForActivation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "ValidatorNotNew", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "ValidatorNotPreDeposited", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + } + ], + "name": "ValidatorNotProven", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "ValueNotMultipleOfPredepositAmount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "WithdrawalCredentialsInvalidVersion", + "type": "error" + }, + { + "inputs": [], + "name": "WithdrawalCredentialsMatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "WithdrawalCredentialsMisformed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "internalType": "address", + "name": "withdrawalCredentialsAddress", + "type": "address" + } + ], + "name": "WithdrawalCredentialsMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "WithdrawalFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "argument", + "type": "string" + } + ], + "name": "ZeroArgument", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPauseDuration", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "total", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "locked", + "type": "uint128" + } + ], + "name": "BalanceLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "BalanceRefunded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "BalanceToppedUp", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "total", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "locked", + "type": "uint128" + } + ], + "name": "BalanceUnlocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "BalanceWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newDepositor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "prevDepositor", + "type": "address" + } + ], + "name": "DepositorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guarantor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "GuarantorRefundAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guarantor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "GuarantorRefundClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGuarantor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "prevGuarantor", + "type": "address" + } + ], + "name": "GuarantorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "ValidatorActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "guaranteeTotal", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "guaranteeLocked", + "type": "uint256" + } + ], + "name": "ValidatorCompensated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "ValidatorPreDeposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes", + "name": "validatorPubkey", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "stakingVault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "ValidatorProven", + "type": "event" + }, + { + "inputs": [], + "name": "ACTIVATION_DEPOSIT_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BEACON_ROOTS", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEPOSIT_DOMAIN", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GI_FIRST_VALIDATOR_CURR", + "outputs": [ + { + "internalType": "GIndex", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GI_FIRST_VALIDATOR_PREV", + "outputs": [ + { + "internalType": "GIndex", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GI_PUBKEY_WC_PARENT", + "outputs": [ + { + "internalType": "GIndex", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GI_STATE_ROOT", + "outputs": [ + { + "internalType": "GIndex", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_SUPPORTED_WC_VERSION", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_TOPUP_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_SUPPORTED_WC_VERSION", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_INFINITELY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PIVOT_SLOT", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PREDEPOSIT_AMOUNT", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_pubkey", + "type": "bytes" + } + ], + "name": "activateValidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + } + ], + "name": "claimGuarantorRefund", + "outputs": [ + { + "internalType": "uint256", + "name": "claimedEther", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_guarantor", + "type": "address" + } + ], + "name": "claimableRefund", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getResumeSinceTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMembers", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_defaultAdmin", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "nodeOperatorBalance", + "outputs": [ + { + "components": [ + { + "internalType": "uint128", + "name": "total", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "locked", + "type": "uint128" + } + ], + "internalType": "struct PredepositGuarantee.NodeOperatorBalance", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "nodeOperatorDepositor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "nodeOperatorGuarantor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_duration", + "type": "uint256" + } + ], + "name": "pauseFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pauseUntilInclusive", + "type": "uint256" + } + ], + "name": "pauseUntil", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IStakingVault", + "name": "_vault", + "type": "address" + } + ], + "name": "pendingActivations", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IStakingVault", + "name": "_stakingVault", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + } + ], + "internalType": "struct IStakingVault.Deposit[]", + "name": "_deposits", + "type": "tuple[]" + }, + { + "components": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "b", + "type": "bytes32" + } + ], + "internalType": "struct BLS12_381.Fp", + "name": "pubkeyY", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "c0_a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c0_b", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c1_a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c1_b", + "type": "bytes32" + } + ], + "internalType": "struct BLS12_381.Fp2", + "name": "signatureY", + "type": "tuple" + } + ], + "internalType": "struct BLS12_381.DepositY[]", + "name": "_depositsY", + "type": "tuple[]" + } + ], + "name": "predeposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_invalidWithdrawalCredentials", + "type": "bytes32" + } + ], + "name": "proveInvalidValidatorWC", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + }, + { + "internalType": "contract IStakingVault", + "name": "_stakingVault", + "type": "address" + } + ], + "name": "proveUnknownValidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness[]", + "name": "_witnesses", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + } + ], + "name": "proveWCActivateAndTopUpValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + } + ], + "name": "proveWCAndActivate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resume", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newDepositor", + "type": "address" + } + ], + "name": "setNodeOperatorDepositor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newGuarantor", + "type": "address" + } + ], + "name": "setNodeOperatorGuarantor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct PredepositGuarantee.ValidatorTopUp[]", + "name": "_topUps", + "type": "tuple[]" + } + ], + "name": "topUpExistingValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "topUpNodeOperatorBalance", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + } + ], + "name": "unlockedBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "unlocked", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "validatePubKeyWCProof", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_validatorPubkey", + "type": "bytes" + } + ], + "name": "validatorStatus", + "outputs": [ + { + "components": [ + { + "internalType": "enum IPredepositGuarantee.ValidatorStage", + "name": "stage", + "type": "uint8" + }, + { + "internalType": "contract IStakingVault", + "name": "stakingVault", + "type": "address" + }, + { + "internalType": "address", + "name": "nodeOperator", + "type": "address" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorStatus", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "depositDataRoot", + "type": "bytes32" + } + ], + "internalType": "struct IStakingVault.Deposit", + "name": "_deposit", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "b", + "type": "bytes32" + } + ], + "internalType": "struct BLS12_381.Fp", + "name": "pubkeyY", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "c0_a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c0_b", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c1_a", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "c1_b", + "type": "bytes32" + } + ], + "internalType": "struct BLS12_381.Fp2", + "name": "signatureY", + "type": "tuple" + } + ], + "internalType": "struct BLS12_381.DepositY", + "name": "_depositsY", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "_withdrawalCredentials", + "type": "bytes32" + } + ], + "name": "verifyDepositMessage", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + } + ], + "name": "withdrawNodeOperatorBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/interfaces/Susds.json b/interfaces/Susds.json new file mode 100644 index 000000000..d17278bde --- /dev/null +++ b/interfaces/Susds.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"usdsJoin_","type":"address"},{"internalType":"address","name":"vow_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"name":"ERC1967InvalidImplementation","type":"error"},{"inputs":[],"name":"ERC1967NonPayable","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"UUPSUnauthorizedCallContext","type":"error"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"name":"UUPSUnsupportedProxiableUUID","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"usr","type":"address"}],"name":"Deny","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"chi","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"diff","type":"uint256"}],"name":"Drip","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"what","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"data","type":"uint256"}],"name":"File","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint16","name":"referral","type":"uint16"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Referral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"usr","type":"address"}],"name":"Rely","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chi","outputs":[{"internalType":"uint192","name":"","type":"uint192"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"deny","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint16","name":"referral","type":"uint16"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"drip","outputs":[{"internalType":"uint256","name":"nChi","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"what","type":"bytes32"},{"internalType":"uint256","name":"data","type":"uint256"}],"name":"file","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getImplementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint16","name":"referral","type":"uint16"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"usr","type":"address"}],"name":"rely","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rho","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ssr","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"usds","outputs":[{"internalType":"contract UsdsLike","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdsJoin","outputs":[{"internalType":"contract UsdsJoinLike","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vat","outputs":[{"internalType":"contract VatLike","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vow","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/interfaces/UpgradeTemplateV3.json b/interfaces/UpgradeTemplateV3.json new file mode 100644 index 000000000..5c0209da1 --- /dev/null +++ b/interfaces/UpgradeTemplateV3.json @@ -0,0 +1,1396 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "oldLocatorImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "oldLidoImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "oldAccountingOracleImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "oldTokenRateNotifier", + "type": "address" + }, + { + "internalType": "address", + "name": "newLocatorImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "newLidoImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "newAccountingOracleImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "newTokenRateNotifier", + "type": "address" + }, + { + "internalType": "address", + "name": "upgradeableBeacon", + "type": "address" + }, + { + "internalType": "address", + "name": "stakingVaultImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "dashboardImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "gateSealForVaults", + "type": "address" + }, + { + "internalType": "address", + "name": "kernel", + "type": "address" + }, + { + "internalType": "address", + "name": "agent", + "type": "address" + }, + { + "internalType": "address", + "name": "aragonAppLidoRepo", + "type": "address" + }, + { + "internalType": "address", + "name": "locator", + "type": "address" + }, + { + "internalType": "address", + "name": "voting", + "type": "address" + }, + { + "internalType": "address", + "name": "dualGovernance", + "type": "address" + }, + { + "internalType": "address", + "name": "acl", + "type": "address" + }, + { + "internalType": "address", + "name": "resealManager", + "type": "address" + }, + { + "internalType": "address", + "name": "easyTrack", + "type": "address" + }, + { + "internalType": "address", + "name": "vaultsAdapter", + "type": "address" + }, + { + "internalType": "address", + "name": "etfAlterTiersInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfRegisterGroupsInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfRegisterTiersInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfUpdateGroupsShareLimitInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfSetJailStatusInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfUpdateVaultsFeesInOperatorGrid", + "type": "address" + }, + { + "internalType": "address", + "name": "etfForceValidatorExitsInVaultHub", + "type": "address" + }, + { + "internalType": "address", + "name": "etfSetLiabilitySharesTargetInVaultHub", + "type": "address" + }, + { + "internalType": "address", + "name": "etfSocializeBadDebtInVaultHub", + "type": "address" + } + ], + "internalType": "struct V3Addresses.V3AddressesParams", + "name": "_params", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_expireSinceInclusive", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_initialMaxExternalRatioBP", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "BurnerMigrationNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "Expired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "repo", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "IncorrectAragonAppImplementation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "burner", + "type": "address" + } + ], + "name": "IncorrectBurnerAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "IncorrectBurnerSharesMigration", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "IncorrectOZAccessControlRoleHolders", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "name": "IncorrectProxyAdmin", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "IncorrectProxyImplementation", + "type": "error" + }, + { + "inputs": [], + "name": "IncorrectTokenRateNotifierObserversLengthMigration", + "type": "error" + }, + { + "inputs": [], + "name": "IncorrectTokenRateNotifierObserversMigration", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "notifier", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "IncorrectTokenRateNotifierOwnerMigration", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beacon", + "type": "address" + }, + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "IncorrectUpgradeableBeaconImplementation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "beacon", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "IncorrectUpgradeableBeaconOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "IncorrectVaultFactoryBeacon", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "delegation", + "type": "address" + } + ], + "name": "IncorrectVaultFactoryDashboardImplementation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "actualVersion", + "type": "uint256" + } + ], + "name": "InvalidContractVersion", + "type": "error" + }, + { + "inputs": [], + "name": "NewAndOldLocatorImplementationsMustBeDifferent", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "contractAddress", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "NonZeroRoleHolders", + "type": "error" + }, + { + "inputs": [], + "name": "OldAndNewTokenRateNotifiersMustBeDifferent", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyAgentCanUpgrade", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "moduleName", + "type": "string" + } + ], + "name": "StakingModuleNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "StartAlreadyCalledInThisTx", + "type": "error" + }, + { + "inputs": [], + "name": "StartAndFinishMustBeInSameTx", + "type": "error" + }, + { + "inputs": [], + "name": "TotalSharesOrPooledEtherChanged", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedEasyTrackFactories", + "type": "error" + }, + { + "inputs": [], + "name": "UpgradeAlreadyFinished", + "type": "error" + }, + { + "inputs": [], + "name": "UpgradeAlreadyStarted", + "type": "error" + }, + { + "anonymous": false, + "inputs": [], + "name": "UpgradeFinished", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "UpgradeStarted", + "type": "event" + }, + { + "inputs": [], + "name": "ACCOUNTING", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACCOUNTING_ORACLE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ACL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "AGENT", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ARAGON_APP_LIDO_REPO", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "BURNER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CSM_ACCOUNTING", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CSM_MODULE_NAME", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CURATED_MODULE_NAME", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DASHBOARD_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DUAL_GOVERNANCE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EASY_TRACK", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EL_REWARDS_VAULT", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_ALTER_TIERS_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_FORCE_VALIDATOR_EXITS_IN_VAULT_HUB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_REGISTER_GROUPS_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_REGISTER_TIERS_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_SET_JAIL_STATUS_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_SET_LIABILITY_SHARES_TARGET_IN_VAULT_HUB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_SOCIALIZE_BAD_DEBT_IN_VAULT_HUB", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETF_UPDATE_VAULTS_FEES_IN_OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EVM_SCRIPT_EXECUTOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPECTED_FINAL_ACCOUNTING_ORACLE_CONSENSUS_VERSION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPECTED_FINAL_ACCOUNTING_ORACLE_VERSION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPECTED_FINAL_LIDO_VERSION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPIRE_SINCE_INCLUSIVE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GATE_SEAL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INFINITE_ALLOWANCE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "INITIAL_MAX_EXTERNAL_RATIO_BP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "KERNEL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LAZY_ORACLE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LOCATOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NEW_ACCOUNTING_ORACLE_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NEW_LIDO_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NEW_LOCATOR_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NEW_TOKEN_RATE_NOTIFIER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "NODE_OPERATORS_REGISTRY", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_ACCOUNTING_ORACLE_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_BURNER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_LIDO_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_LOCATOR_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OLD_TOKEN_RATE_NOTIFIER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "OPERATOR_GRID", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ORACLE_DAEMON_CONFIG", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ORACLE_REPORT_SANITY_CHECKER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PREDEPOSIT_GUARANTEE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESEAL_MANAGER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SIMPLE_DVT", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SIMPLE_DVT_MODULE_NAME", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "STAKING_ROUTER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "STAKING_VAULT_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADEABLE_BEACON", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADE_NOT_STARTED", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADE_STARTED_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VALIDATORS_EXIT_BUS_ORACLE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VAULTS_ADAPTER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VAULT_FACTORY", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VAULT_HUB", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VOTING", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWAL_QUEUE", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WSTETH", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "contractsWithBurnerAllowances", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "finishUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "initialOldBurnerStethSharesBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialTotalPooledEther", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialTotalShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isUpgradeFinished", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "upgradeBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/UpgradeableBeacon.json b/interfaces/UpgradeableBeacon.json new file mode 100644 index 000000000..ada815fee --- /dev/null +++ b/interfaces/UpgradeableBeacon.json @@ -0,0 +1,142 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + }, + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "BeaconInvalidImplementation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/interfaces/V3LaunchOmnibus.json b/interfaces/V3LaunchOmnibus.json new file mode 100644 index 000000000..6f6fcb07a --- /dev/null +++ b/interfaces/V3LaunchOmnibus.json @@ -0,0 +1,143 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "proposalMetadata", + "type": "string" + } + ], + "name": "getEVMScript", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "internalType": "string", + "name": "proposalMetadata", + "type": "string" + } + ], + "name": "getNewVoteCallBytecode", + "outputs": [ + { + "internalType": "bytes", + "name": "newVoteBytecode", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVoteItems", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OmnibusBase.ScriptCall", + "name": "call", + "type": "tuple" + } + ], + "internalType": "struct OmnibusBase.VoteItem[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVotingVoteItems", + "outputs": [ + { + "components": [ + { + "internalType": "string", + "name": "description", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct OmnibusBase.ScriptCall", + "name": "call", + "type": "tuple" + } + ], + "internalType": "struct OmnibusBase.VoteItem[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "voteId", + "type": "uint256" + }, + { + "internalType": "string", + "name": "proposalMetadata", + "type": "string" + } + ], + "name": "isValidVoteScript", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/VaultFactory.json b/interfaces/VaultFactory.json new file mode 100644 index 000000000..c6f34d9c1 --- /dev/null +++ b/interfaces/VaultFactory.json @@ -0,0 +1,301 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_lidoLocator", + "type": "address" + }, + { + "internalType": "address", + "name": "_beacon", + "type": "address" + }, + { + "internalType": "address", + "name": "_dashboardImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "_previousFactory", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CloneArgumentsTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "FailedDeployment", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientFunds", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "argument", + "type": "string" + } + ], + "name": "ZeroArgument", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "dashboard", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "admin", + "type": "address" + } + ], + "name": "DashboardCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultCreated", + "type": "event" + }, + { + "inputs": [], + "name": "BEACON", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DASHBOARD_IMPL", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PREVIOUS_FACTORY", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_defaultAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "address", + "name": "_nodeOperatorManager", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nodeOperatorFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_confirmExpiry", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "internalType": "struct Permissions.RoleAssignment[]", + "name": "_roleAssignments", + "type": "tuple[]" + } + ], + "name": "createVaultWithDashboard", + "outputs": [ + { + "internalType": "contract IStakingVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "contract Dashboard", + "name": "dashboard", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_defaultAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "_nodeOperator", + "type": "address" + }, + { + "internalType": "address", + "name": "_nodeOperatorManager", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_nodeOperatorFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_confirmExpiry", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "internalType": "struct Permissions.RoleAssignment[]", + "name": "_roleAssignments", + "type": "tuple[]" + } + ], + "name": "createVaultWithDashboardWithoutConnectingToVaultHub", + "outputs": [ + { + "internalType": "contract IStakingVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "contract Dashboard", + "name": "dashboard", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "deployedVaults", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/interfaces/VaultHub.json b/interfaces/VaultHub.json new file mode 100644 index 000000000..d10e932d8 --- /dev/null +++ b/interfaces/VaultHub.json @@ -0,0 +1,2606 @@ +[ + { + "inputs": [ + { + "internalType": "contract ILidoLocator", + "name": "_locator", + "type": "address" + }, + { + "internalType": "contract ILido", + "name": "_lido", + "type": "address" + }, + { + "internalType": "contract IHashConsensus", + "name": "_consensusContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxRelativeShareLimitBP", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "AlreadyConnected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "totalValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "AmountExceedsTotalValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "withdrawable", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "requested", + "type": "uint256" + } + ], + "name": "AmountExceedsWithdrawableValue", + "type": "error" + }, + { + "inputs": [], + "name": "BadDebtSocializationNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ForcedValidatorExitNotAllowed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "InsufficientSharesToBurn", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "InsufficientStagedBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "etherToLock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLockableValue", + "type": "uint256" + } + ], + "name": "InsufficientValue", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "valueBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxValueBP", + "type": "uint256" + } + ], + "name": "InvalidBasisPoints", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NoFundsForForceRebalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "unsettledLidoFees", + "type": "uint256" + } + ], + "name": "NoFundsToSettleLidoFees", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liabilityShares", + "type": "uint256" + } + ], + "name": "NoLiabilitySharesShouldBeLeft", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NoReasonForForceRebalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "unsettledLidoFees", + "type": "uint256" + } + ], + "name": "NoUnsettledLidoFeesShouldBeLeft", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NoUnsettledLidoFeesToSettle", + "type": "error" + }, + { + "inputs": [], + "name": "NotAuthorized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "NotConnectedToHub", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "PDGNotDepositor", + "type": "error" + }, + { + "inputs": [], + "name": "PartialValidatorWithdrawalNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "PauseIntentAlreadySet", + "type": "error" + }, + { + "inputs": [], + "name": "PauseIntentAlreadyUnset", + "type": "error" + }, + { + "inputs": [], + "name": "PauseUntilMustBeInFuture", + "type": "error" + }, + { + "inputs": [], + "name": "PausedExpected", + "type": "error" + }, + { + "inputs": [], + "name": "ResumedExpected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "value", + "type": "int256" + } + ], + "name": "SafeCastOverflowedIntToUint", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "expectedSharesAfterMint", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + } + ], + "name": "ShareLimitExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxShareLimit", + "type": "uint256" + } + ], + "name": "ShareLimitTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultHubNotPendingOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expectedBalance", + "type": "uint256" + } + ], + "name": "VaultInsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultIsDisconnecting", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "totalValue", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "newRebalanceThresholdBP", + "type": "uint256" + } + ], + "name": "VaultMintingCapacityExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultNotFactoryDeployed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultOssified", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultReportStale", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroArgument", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPauseDuration", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vaultDonor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "vaultAcceptor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtShares", + "type": "uint256" + } + ], + "name": "BadDebtSocialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "badDebtShares", + "type": "uint256" + } + ], + "name": "BadDebtWrittenOffToBeInternalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "pauseIntent", + "type": "bool" + } + ], + "name": "BeaconChainDepositsPauseIntentSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOfShares", + "type": "uint256" + } + ], + "name": "BurnedSharesOnVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "pubkeys", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "address", + "name": "refundRecipient", + "type": "address" + } + ], + "name": "ForcedValidatorExitTriggered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "transferred", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cumulativeLidoFees", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "settledLidoFees", + "type": "uint256" + } + ], + "name": "LidoFeesSettled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOfShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "lockedAmount", + "type": "uint256" + } + ], + "name": "MintedSharesOnVault", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "Resumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "name": "VaultConnected", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "nodeOperator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shareLimit", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reserveRatioBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "forcedRebalanceThresholdBP", + "type": "uint256" + } + ], + "name": "VaultConnectionUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashingReserve", + "type": "uint256" + } + ], + "name": "VaultDisconnectAborted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultDisconnectCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "VaultDisconnectInitiated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "preInfraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "preLiquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "preReservationFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "infraFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "liquidityFeeBP", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reservationFeeBP", + "type": "uint256" + } + ], + "name": "VaultFeesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "inOutDelta", + "type": "int256" + } + ], + "name": "VaultInOutDeltaUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + } + ], + "name": "VaultOwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sharesBurned", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "etherWithdrawn", + "type": "uint256" + } + ], + "name": "VaultRebalanced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "redemptionShares", + "type": "uint256" + } + ], + "name": "VaultRedemptionSharesUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportTimestamp", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportTotalValue", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "reportInOutDelta", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportCumulativeLidoFees", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportLiabilityShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportMaxLiabilityShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportSlashingReserve", + "type": "uint256" + } + ], + "name": "VaultReportApplied", + "type": "event" + }, + { + "inputs": [], + "name": "BAD_DEBT_MASTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONNECT_DEPOSIT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "CONSENSUS_CONTRACT", + "outputs": [ + { + "internalType": "contract IHashConsensus", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO", + "outputs": [ + { + "internalType": "contract ILido", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIDO_LOCATOR", + "outputs": [ + { + "internalType": "contract ILidoLocator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_RELATIVE_SHARE_LIMIT_BP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_INFINITELY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSE_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REDEMPTION_MASTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REPORT_FRESHNESS_DELTA", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VALIDATOR_EXIT_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VAULT_MASTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_reportTimestamp", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reportTotalValue", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "_reportInOutDelta", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "_reportCumulativeLidoFees", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reportLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reportMaxLiabilityShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reportSlashingReserve", + "type": "uint256" + } + ], + "name": "applyVaultReport", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "badDebtToInternalize", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "burnShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "collectERC20FromVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "connectVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "decreaseInternalizedBadDebt", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "disconnect", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "forceRebalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_pubkeys", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_refundRecipient", + "type": "address" + } + ], + "name": "forceValidatorExit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "fund", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getResumeSinceTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMembers", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "healthShortfallShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_badDebtVault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxSharesToInternalize", + "type": "uint256" + } + ], + "name": "internalizeBadDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isPendingDisconnect", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isReportFresh", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isVaultConnected", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "isVaultHealthy", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "latestReport", + "outputs": [ + { + "components": [ + { + "internalType": "uint104", + "name": "totalValue", + "type": "uint104" + }, + { + "internalType": "int104", + "name": "inOutDelta", + "type": "int104" + }, + { + "internalType": "uint48", + "name": "timestamp", + "type": "uint48" + } + ], + "internalType": "struct VaultHub.Report", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "liabilityShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "locked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "maxLockableValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "mintShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "obligations", + "outputs": [ + { + "internalType": "uint256", + "name": "sharesToBurn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feesToSettle", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "obligationsShortfallValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "pauseBeaconChainDeposits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_duration", + "type": "uint256" + } + ], + "name": "pauseFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_pauseUntilInclusive", + "type": "uint256" + } + ], + "name": "pauseUntil", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "components": [ + { + "internalType": "bytes32[]", + "name": "proof", + "type": "bytes32[]" + }, + { + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validatorIndex", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "childBlockTimestamp", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "slot", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "proposerIndex", + "type": "uint64" + } + ], + "internalType": "struct IPredepositGuarantee.ValidatorWitness", + "name": "_witness", + "type": "tuple" + } + ], + "name": "proveUnknownValidatorToPDG", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shares", + "type": "uint256" + } + ], + "name": "rebalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_pubkeys", + "type": "bytes" + } + ], + "name": "requestValidatorExit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "resume", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "resumeBeaconChainDeposits", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_liabilitySharesTarget", + "type": "uint256" + } + ], + "name": "setLiabilitySharesTarget", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "settleLidoFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "settleableLidoFeesValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_badDebtVault", + "type": "address" + }, + { + "internalType": "address", + "name": "_vaultAcceptor", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxSharesToSocialize", + "type": "uint256" + } + ], + "name": "socializeBadDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "int256", + "name": "_deltaValue", + "type": "int256" + } + ], + "name": "totalMintingCapacityShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "totalValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amountOfShares", + "type": "uint256" + } + ], + "name": "transferAndBurnShares", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_newOwner", + "type": "address" + } + ], + "name": "transferVaultOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_pubkeys", + "type": "bytes" + }, + { + "internalType": "uint64[]", + "name": "_amountsInGwei", + "type": "uint64[]" + }, + { + "internalType": "address", + "name": "_refundRecipient", + "type": "address" + } + ], + "name": "triggerValidatorWithdrawals", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_shareLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reserveRatioBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_forcedRebalanceThresholdBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_infraFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_liquidityFeeBP", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_reservationFeeBP", + "type": "uint256" + } + ], + "name": "updateConnection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_index", + "type": "uint256" + } + ], + "name": "vaultByIndex", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultConnection", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint96", + "name": "shareLimit", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "vaultIndex", + "type": "uint96" + }, + { + "internalType": "uint48", + "name": "disconnectInitiatedTs", + "type": "uint48" + }, + { + "internalType": "uint16", + "name": "reserveRatioBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "forcedRebalanceThresholdBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "infraFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "liquidityFeeBP", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "reservationFeeBP", + "type": "uint16" + }, + { + "internalType": "bool", + "name": "beaconChainDepositsPauseIntent", + "type": "bool" + } + ], + "internalType": "struct VaultHub.VaultConnection", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "vaultRecord", + "outputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint104", + "name": "totalValue", + "type": "uint104" + }, + { + "internalType": "int104", + "name": "inOutDelta", + "type": "int104" + }, + { + "internalType": "uint48", + "name": "timestamp", + "type": "uint48" + } + ], + "internalType": "struct VaultHub.Report", + "name": "report", + "type": "tuple" + }, + { + "internalType": "uint96", + "name": "maxLiabilityShares", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "liabilityShares", + "type": "uint96" + }, + { + "components": [ + { + "internalType": "int104", + "name": "value", + "type": "int104" + }, + { + "internalType": "int104", + "name": "valueOnRefSlot", + "type": "int104" + }, + { + "internalType": "uint48", + "name": "refSlot", + "type": "uint48" + } + ], + "internalType": "struct DoubleRefSlotCache.Int104WithCache[2]", + "name": "inOutDelta", + "type": "tuple[2]" + }, + { + "internalType": "uint128", + "name": "minimalReserve", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "redemptionShares", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "cumulativeLidoFees", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "settledLidoFees", + "type": "uint128" + } + ], + "internalType": "struct VaultHub.VaultRecord", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vaultsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "voluntaryDisconnect", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_ether", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + } + ], + "name": "withdrawableValue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] diff --git a/scripts/_vote_2025_MM_DD.py b/scripts/_vote_2025_MM_DD.py index a3047aa7e..1fb3c7969 100644 --- a/scripts/_vote_2025_MM_DD.py +++ b/scripts/_vote_2025_MM_DD.py @@ -48,11 +48,11 @@ def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: # (dg_item_address_2, dg_item_encoded_input_2) # ]), # ] - # + # # dg_call_script = submit_proposals([ # (dg_items, "TODO DG proposal description") # ]) - # + # # vote_desc_items, call_script_items = zip( # ( # "TODO 1. DG submission description", diff --git a/scripts/ci/prepare_environment.py b/scripts/ci/prepare_environment.py index 0c89dc6ea..3c72a3cea 100644 --- a/scripts/ci/prepare_environment.py +++ b/scripts/ci/prepare_environment.py @@ -2,16 +2,21 @@ from typing import Callable, Tuple, List +from tests.conftest import Helpers + from utils.config import contracts, get_deployer_account from utils.voting import bake_vote_items from utils.dual_governance import process_pending_proposals from utils.evm_script import encode_call_script from utils.import_current_votes import get_vote_script_files, get_upgrade_script_files from utils.mainnet_fork import pass_and_exec_dao_vote +from utils.test.governance_helpers import execute_vote_and_process_dg_proposals def main(): process_pending_proposals() - execute_votings_and_process_created_proposals() + # TODO revert after December Aragon + #execute_votings_and_process_created_proposals() + execute_vote_and_process_dg_proposals(Helpers, -1, -1) def execute_votings_and_process_created_proposals(): votings_in_flight = retrieve_votings_in_flight() diff --git a/scripts/upgrade_2025_12_15_mainnet_v3.py b/scripts/upgrade_2025_12_15_mainnet_v3.py new file mode 100644 index 000000000..b9b0a4c69 --- /dev/null +++ b/scripts/upgrade_2025_12_15_mainnet_v3.py @@ -0,0 +1,136 @@ +""" +# Vote 2025_12_15 + +=== 1. DG PROPOSAL === +1.1. Check execution time window (14:00–23:00 UTC) +1.2. Call V3Template.startUpgrade() +1.3. Upgrade LidoLocator implementation +1.4. Grant APP_MANAGER_ROLE to Agent +1.5. Set Lido implementation in Aragon Kernel +1.6. Revoke APP_MANAGER_ROLE from Agent +1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido on old Burner +1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated Module on old Burner +1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT on old Burner +1.10. Revoke REQUEST_BURN_SHARES_ROLE from CSM Accounting on old Burner +1.11. Upgrade AccountingOracle implementation +1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido on StakingRouter +1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting on StakingRouter +1.14. Grant CONFIG_MANAGER_ROLE to Agent on OracleDaemonConfig +1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT in OracleDaemonConfig +1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT in OracleDaemonConfig +1.17. Revoke CONFIG_MANAGER_ROLE from Agent on OracleDaemonConfig +1.18. Grant PAUSE_ROLE to Agent +1.19. Pause PredepositGuarantee +1.20. Revoke PAUSE_ROLE from Agent +1.21. Call V3Template.finishUpgrade() + +=== NON-DG ITEMS === +2. Add AlterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid.alterTiers) +3. Add RegisterGroupsInOperatorGrid factory to Easy Track (permissions: operatorGrid.registerGroup, operatorGrid.registerTiers) +4. Add RegisterTiersInOperatorGrid factory to Easy Track (permissions: operatorGrid.registerTiers) +5. Add UpdateGroupsShareLimitInOperatorGrid factory to Easy Track (permissions: operatorGrid.updateGroupShareLimit) +6. Add SetJailStatusInOperatorGrid factory to Easy Track (permissions: vaultsAdapter.setVaultJailStatus) +7. Add UpdateVaultsFeesInOperatorGrid factory to Easy Track (permissions: vaultsAdapter.updateVaultFees) +8. Add ForceValidatorExitsInVaultHub factory to Easy Track (permissions: vaultsAdapter.forceValidatorExit) +9. Add SocializeBadDebtInVaultHub factory to Easy Track (permissions: vaultsAdapter.socializeBadDebt) + +# TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. +""" + +from typing import Dict, List, Tuple + +from utils.voting import bake_vote_items, confirm_vote_script, create_vote +from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description +from utils.config import get_deployer_account, get_is_live, get_priority_fee +from utils.mainnet_fork import pass_and_exec_dao_vote +from utils.dual_governance import submit_proposals +from brownie import interface + + +# ============================== Addresses =================================== +OMNIBUS_CONTRACT = "0xE1F4c16908fCE6935b5Ad38C6e3d58830fe86442" + + +# ============================= Description ================================== +IPFS_DESCRIPTION = """ +**Activate Lido V3: Phase 1 (Soft Launch)** — a major upgrade to the Lido protocol introduces non-custodial, over-collateralized staking vaults (“stVaults”) that enable stakers to opt into specific operators or strategies while still minting stETH. The Lido V3 design and implementation follow the DAO-approved [Snapshot](https://snapshot.box/#/s:lido-snapshot.eth/proposal/0x01cd474645cc7c3ddf68314d475d421ef833499297f508fee5f7411fafff3954). Phase 1 is intentionally [proposed in a constrained soft launch](https://research.lido.fi/t/lido-v3-design-implementation-proposal/10665/8) mode to enable early adopters and partners while maintaining a conservative security posture. + +Deployment verification: [MixBytes](https://github.com/lidofinance/audits/blob/main/MixBytes%20Lido%20V3%20Security%20Audit%20Report%20-%2012-2025.pdf) | Formal verification: [Certora](https://github.com/lidofinance/audits/blob/main/Certora%20Lido%20V3%20Formal%20Verification%20Report%20-%2012-2025.pdf) | Audits: [MixBytes](https://github.com/lidofinance/audits/blob/main/MixBytes%20Lido%20V3%20Security%20Audit%20Report%20-%2012-2025.pdf), [Certora](https://github.com/lidofinance/audits/blob/main/Certora%20Lido%20V3%20Audit%20Report%20-%2012-2025.pdf), [Consensys Diligence](https://github.com/lidofinance/audits/blob/main/Consensys%20Diligence%20Lido%20V3%20Security%20Audit%20-%2011-2025.pdf) | Offchain audits: [Certora](https://github.com/lidofinance/audits/blob/main/Certora%20Lido%20V3%20Oracle%20V7%20Audit%20Report%20-%2012-2025.pdf), [Composable Security](https://github.com/lidofinance/audits/blob/main/Composable%20Security%20Lido%20V3%20Oracle%20V7%20Audit%20Report%20-%2012-2025.pdf) + +[Dual Governance Items](https://research.lido.fi/t/lido-v3-design-implementation-proposal/10665/5#p-23638-part-2-dual-governance-items-21-items-subject-to-dg-veto-period-27) +- Ensure DG proposal execution occurs during the monitored time window and aligns with oracle reports. Item 1.1. +- Lock upgrade window and validate network state. Item 1.2. +- Upgrade proxy implementations, reassign roles and permissions, configure oracle slashing parameters. Items 1.3-1.17. +- Disable Predeposit Guarantee guided deposit flows as a part of [Soft Launch](https://research.lido.fi/t/lido-v3-design-implementation-proposal/10665/9). Items 1.18-1.20. +- Finalize upgrade and validate Lido V3 activation state. Item 1.21. + +[Voting Items](https://research.lido.fi/t/lido-v3-design-implementation-proposal/10665/5#p-23638-part-1-voting-items-8-items-execute-immediately-26) +- Add Easy Track factories enabling [stVault Committee](https://docs.lido.fi/multisigs/committees#216-stvaults-committee) to configure VaultHub and OperatorGrid contracts. Items 2–9.""" +DG_PROPOSAL_DESCRIPTION = "Activate Lido V3: Phase 1 (Soft Launch)" +DG_SUBMISSION_DESCRIPTION = "1. Submit a Dual Governance proposal to activate Lido V3: Phase 1 (Soft Launch)" + + +# ================================ Main ====================================== +def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: + vote_desc_items = [] + call_script_items = [] + + # 1. receive DG vote items from omnibus contract + contract_dg_items = interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).getVoteItems() + + dg_items = [] + for _, call_script in contract_dg_items: + dg_items.append((call_script[0], call_script[1].hex())) + + dg_call_script = submit_proposals([ + (dg_items, DG_PROPOSAL_DESCRIPTION) + ]) + + vote_desc_items.append(DG_SUBMISSION_DESCRIPTION) + call_script_items.append(dg_call_script[0]) + + # 2. receive non-DG vote items from omnibus contract + voting_items = interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).getVotingVoteItems() + + for desc, call_script in voting_items: + vote_desc_items.append(desc) + call_script_items.append((call_script[0], call_script[1].hex())) + + return vote_desc_items, call_script_items + + +def start_vote(tx_params: Dict[str, str], silent: bool = False): + vote_desc_items, call_script_items = get_vote_items() + vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) + + desc_ipfs = ( + calculate_vote_ipfs_description(IPFS_DESCRIPTION) + if silent else upload_vote_ipfs_description(IPFS_DESCRIPTION) + ) + + vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list( + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) + ) + + assert interface.V3LaunchOmnibus(OMNIBUS_CONTRACT).isValidVoteScript(vote_id, DG_PROPOSAL_DESCRIPTION) + + return vote_id, tx + + +def main(): + tx_params: Dict[str, str] = {"from": get_deployer_account().address} + if get_is_live(): + tx_params["priority_fee"] = get_priority_fee() + + vote_id, _ = start_vote(tx_params=tx_params, silent=False) + vote_id >= 0 and print(f"Vote created: {vote_id}.") + + +def start_and_execute_vote_on_fork_manual(): + if get_is_live(): + raise Exception("This script is for local testing only.") + + tx_params = {"from": get_deployer_account()} + vote_id, _ = start_vote(tx_params=tx_params, silent=True) + print(f"Vote created: {vote_id}.") + pass_and_exec_dao_vote(int(vote_id), step_by_step=True) diff --git a/scripts/vote_2025_12_15.py b/scripts/vote_2025_12_15.py new file mode 100644 index 000000000..bf69b8096 --- /dev/null +++ b/scripts/vote_2025_12_15.py @@ -0,0 +1,348 @@ +""" +# Vote 2025_12_15 + +=== 1. DG PROPOSAL === +I. Change Curated Module fees +1.1. Change Curated Module (MODULE_ID = 1) fees in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999: Module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP + +II. Raise SDVT module stake share limit +1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP and priority exit threshold from 444 BP to 478 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + +III. Set A41 soft target validator limit to 0 +1.3. Set soft-mode target validators limit to 0 for Node operator A41 (ID = 32) in Curated Module (MODULE_ID = 1) in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + +IV. Set Easy Track TRP limit +1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + +=== NON-DG ITEMS === +V. Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance +2. Temporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e +3. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca +4. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e +5. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 +6. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS + +VI. Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig +7. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5 + +# TODO (after vote) Vote #{vote number} passed & executed on ${date+time}, block ${blockNumber}. +""" + +from typing import Dict, List, Tuple, NamedTuple +from brownie import interface, ZERO_ADDRESS, convert, web3 + +from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op +from utils.finance import make_matic_payout +from utils.voting import bake_vote_items, confirm_vote_script, create_vote +from utils.ipfs import upload_vote_ipfs_description, calculate_vote_ipfs_description +from utils.config import get_deployer_account, get_is_live, get_priority_fee +from utils.mainnet_fork import pass_and_exec_dao_vote +from utils.dual_governance import submit_proposals +from utils.agent import agent_forward +from utils.permissions import encode_permission_revoke, encode_permission_grant_p +from utils.allowed_recipients_registry import set_limit_parameters + + +# ============================== Types =================================== +class TokenLimit(NamedTuple): + address: str + limit: int + + +# ============================== Addresses =================================== +ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" +FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" +ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" +STABLECOINS_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" + + +# ============================== Roles =================================== +CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" +ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" + + +# ============================== Tokens =================================== +SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" +USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" +DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" +LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" +STETH_TOKEN = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" + + +# ============================== Constants =================================== +CURATED_MODULE_ID = 1 +CURATED_MODULE_TARGET_SHARE_BP = 10000 +CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 10000 +CURATED_MODULE_NEW_MODULE_FEE_BP = 350 +CURATED_MODULE_NEW_TREASURY_FEE_BP = 650 +CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + +SDVT_MODULE_ID = 2 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 +SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP = 478 +SDVT_MODULE_MODULE_FEE_BP = 800 +SDVT_MODULE_TREASURY_FEE_BP = 200 +SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + +TRP_PERIOD_DURATION_MONTHS = 12 +TRP_NEW_LIMIT = 15_000_000 * 10**18 + +MATIC_FOR_TRANSFER = 508_106 * 10**18 + +A41_NO_ID = 32 +NO_TARGET_LIMIT_SOFT_MODE = 1 +NEW_A41_TARGET_LIMIT = 0 + +def amount_limits() -> List[Param]: + ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) + eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) + steth_limit = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) + dai_limit = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) + usdc_limit = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) + usdt_limit = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) + susds_limit = TokenLimit(SUSDS_TOKEN, 2_000_000 * (10**18)) + + token_arg_index = 0 + amount_arg_index = 2 + + return [ + # 0: if (1) then (2) else (3) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) + ), + # 1: (_token == stETH) + Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit.address)), + # 2: { return _amount <= 1_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit.limit)), + # + # 3: else if (4) then (5) else (6) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) + ), + # 4: (_token == DAI) + Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit.address)), + # 5: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit.limit)), + # + # 6: else if (7) then (8) else (9) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) + ), + # 7: (_token == LDO) + Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit.address)), + # 8: { return _amount <= 5_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit.limit)), + # + # 9: else if (10) then (11) else (12) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=10, success=11, failure=12), + ), + # 10: (_token == USDC) + Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit.address)), + # 11: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit.limit)), + # + # 12: else if (13) then (14) else (15) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=13, success=14, failure=15), + ), + # 13: (_token == USDT) + Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit.address)), + # 14: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit.limit)), + # + # 15: else if (16) then (17) else (18) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=16, success=17, failure=18), + ), + # 16: (_token == ETH) + Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit.address)), + # 17: { return _amount <= 1000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit.limit)), + # + # 18: else if (19) then (20) else (21) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=19, success=20, failure=21), + ), + # 19: (_token == sUSDS) + Param(token_arg_index, Op.EQ, ArgumentValue(susds_limit.address)), + # 20: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(susds_limit.limit)), + # + # 21: else { return false } + Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), + ] + + +# ============================= Description ================================== +IPFS_DESCRIPTION = """ +1. **Change Curated Staking Module fee to 3.5%**, as per [Snapshot decision](https://snapshot.box/#/s:lido-snapshot.eth/proposal/0x3a5d10fcd3fad6d5ccf05f5bd49244046600ad9cbed9a5e07845200b3ae97e09). Item 1.1. +2. **Raise SDVT Staking Module stake share limit to 4.3% and priority exit threshold to 4.78%**, as [proposed on the Forum](https://research.lido.fi/t/staking-router-module-proposal-simple-dvt/5625/127). Item 1.2. +3. **Set A41 Node Operator soft target validators limit to 0**, as [requested on the Forum](https://research.lido.fi/t/a41-node-operator-intention-to-wind-down-operations-request-for-dao-vote/10954). Item 1.3. +4. **Set Easy Track TRP limit to 15'000'000 LDO**, as per [Snapshot decision](https://snapshot.box/#/s:lido-snapshot.eth/proposal/0x16ecb51631d67213d44629444fcc6275bc2abe4d7e955bebaf15c60a42cba471). Item 1.4. +5. **Add sUSDS token to stablecoins Allowed Tokens Registry and sUSDS transfer permission to Easy Track EVM Script Executor in Aragon Finance**, as proposed in [TMC-6 on the Forum](https://research.lido.fi/t/tmc-6-convert-dao-treasury-stablecoins-into-susds-and-update-config-on-easy-track-and-aragon-finance-accordingly/10868). Items 2-6. +6. **Transfer MATIC from Lido Treasury to Liquidity Observation Lab (LOL) Multisig**, as proposed in [TMC-5 on the Forum](https://research.lido.fi/t/tmc-5-convert-matic-to-usdc/10814). Item 7.""" + + +# ================================ Main ====================================== +def get_vote_items() -> Tuple[List[str], List[Tuple[str, str]]]: + + staking_router = interface.StakingRouter(STAKING_ROUTER) + stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) + + dg_items = [ + agent_forward([ + # 1.1. Change Curated Module (MODULE_ID = 1) module fee from 500 BP to 350 BP and Treasury fee from 500 BP to 650 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + CURATED_MODULE_ID, + CURATED_MODULE_TARGET_SHARE_BP, + CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + CURATED_MODULE_NEW_MODULE_FEE_BP, + CURATED_MODULE_NEW_TREASURY_FEE_BP, + CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK, + CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), + agent_forward([ + # 1.2. Raise SDVT (MODULE_ID = 2) stake share limit from 400 BP to 430 BP and priority exit threshold from 444 BP to 478 BP in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + SDVT_MODULE_ID, + SDVT_MODULE_NEW_TARGET_SHARE_BP, + SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_MODULE_FEE_BP, + SDVT_MODULE_TREASURY_FEE_BP, + SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, + SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), + agent_forward([ + # 1.3. Set soft-mode target validators limit to 0 for Node operator A41 (ID = 32) in Curated Module (MODULE_ID = 1) in Staking Router 0xFdDf38947aFB03C621C71b06C9C70bce73f12999 + ( + staking_router.address, + staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_SOFT_MODE, NEW_A41_TARGET_LIMIT), + ) + ]), + agent_forward([ + # 1.4. Set limit for Easy Track TRP registry 0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8 to 15'000'000 LDO with unchanged period duration of 12 months + set_limit_parameters( + limit=TRP_NEW_LIMIT, + period_duration_months=TRP_PERIOD_DURATION_MONTHS, + registry_address=ET_TRP_REGISTRY, + ), + ]), + ] + + dg_call_script = submit_proposals([ + (dg_items, "Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, set Easy Track TRP limit") + ]) + + vote_desc_items, call_script_items = zip( + ( + "1. Submit a Dual Governance proposal to change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, set Easy Track TRP limit", + dg_call_script[0] + ), + ( + "2. Temporarily grant ADD_TOKEN_TO_ALLOWED_LIST_ROLE to Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", + ( + stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.grantRole.encode_input( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING, + ) + ), + ), + ( + "3. Add sUSDS token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD to stablecoins Allowed Tokens Registry 0x4AC40c34f8992bb1e5E856A448792158022551ca", + (stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.addToken.encode_input(SUSDS_TOKEN)) + ), + ( + "4. Revoke ADD_TOKEN_TO_ALLOWED_LIST_ROLE from Aragon Voting 0x2e59A20f205bB85a89C53f1936454680651E618e", + ( + stablecoins_allowed_tokens_registry.address, stablecoins_allowed_tokens_registry.revokeRole.encode_input( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING, + ) + ) + ), + ( + "5. Revoke CREATE_PAYMENTS_ROLE from Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86", + encode_permission_revoke( + target_app=FINANCE, + permission_name=CREATE_PAYMENTS_ROLE, + revoke_from=ET_EVM_SCRIPT_EXECUTOR, + ), + ), + ( + "6. Grant CREATE_PAYMENTS_ROLE to Easy Track EVM Script Executor 0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977 on Aragon Finance 0xB9E5CBB9CA5b0d659238807E84D0176930753d86 with appended transfer limit of 2,000,000 sUSDS", + encode_permission_grant_p( + target_app=FINANCE, + permission_name=CREATE_PAYMENTS_ROLE, + grant_to=ET_EVM_SCRIPT_EXECUTOR, + params=amount_limits(), + ), + ), + ( + "7. Transfer 508,106 MATIC 0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 from Aragon Agent 0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c to Liquidity Observation Lab (LOL) Multisig 0x87D93d9B2C672bf9c9642d853a8682546a5012B5", + make_matic_payout( + target_address=LOL_MS, + matic_in_wei=MATIC_FOR_TRANSFER, + reference="Transfer 508,106 MATIC from Treasury to Liquidity Observation Lab (LOL) Multisig", + ), + ), + ) + + return vote_desc_items, call_script_items + + +def start_vote(tx_params: Dict[str, str], silent: bool = False): + vote_desc_items, call_script_items = get_vote_items() + vote_items = bake_vote_items(list(vote_desc_items), list(call_script_items)) + + desc_ipfs = ( + calculate_vote_ipfs_description(IPFS_DESCRIPTION) + if silent else upload_vote_ipfs_description(IPFS_DESCRIPTION) + ) + + vote_id, tx = confirm_vote_script(vote_items, silent, desc_ipfs) and list( + create_vote(vote_items, tx_params, desc_ipfs=desc_ipfs) + ) + + return vote_id, tx + + +def main(): + tx_params: Dict[str, str] = {"from": get_deployer_account().address} + if get_is_live(): + tx_params["priority_fee"] = get_priority_fee() + + vote_id, _ = start_vote(tx_params=tx_params, silent=False) + vote_id >= 0 and print(f"Vote created: {vote_id}.") + + +def start_and_execute_vote_on_fork_manual(): + if get_is_live(): + raise Exception("This script is for local testing only.") + + tx_params = {"from": get_deployer_account()} + vote_id, _ = start_vote(tx_params=tx_params, silent=True) + print(f"Vote created: {vote_id}.") + pass_and_exec_dao_vote(int(vote_id), step_by_step=True) diff --git a/sitecustomize.py b/sitecustomize.py new file mode 100644 index 000000000..1f403eb18 --- /dev/null +++ b/sitecustomize.py @@ -0,0 +1,9 @@ +# sitecustomize.py +try: + import solcx.install as solcx_install +except ImportError: + # py-solc-x not installed in this environment; nothing to patch + pass +else: + # replace outdated solc binaries URL with the current one + solcx_install.BINARY_DOWNLOAD_BASE = "https://binaries.soliditylang.org/{}-amd64/{}" \ No newline at end of file diff --git a/tests/_test_2025_MM_DD.py b/tests/_test_2025_MM_DD.py index cb3c66413..7c01f66ed 100644 --- a/tests/_test_2025_MM_DD.py +++ b/tests/_test_2025_MM_DD.py @@ -1,16 +1,19 @@ from brownie import chain, interface from brownie.network.transaction import TransactionReceipt +import pytest from utils.test.tx_tracing_helpers import ( group_voting_events_from_receipt, group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events ) from utils.evm_script import encode_call_script from utils.voting import find_metadata_by_vote_id from utils.ipfs import get_lido_vote_cid_from_str from utils.dual_governance import PROPOSAL_STATUS from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event -) # ============================================================================ @@ -27,8 +30,10 @@ # NOTE: these addresses might have a different value on other chains VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" -DUAL_GOVERNANCE = "0xcdF49b058D606AD34c5789FD8c3BF8B3E54bA2db" +DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" # TODO Set variable to None if item is not presented EXPECTED_VOTE_ID = 1 @@ -40,26 +45,29 @@ @pytest.fixture(scope="module") def dual_governance_proposal_calls(): - # TODO list all Dual Governance proposal calls for events checking - # csm = interface.CSModule(CSM) - # staking_router = interface.StakingRouter(STAKING_ROUTER) - # csm_module_manager_role = csm.MODULE_MANAGER_ROLE() - # csm_verifier_role = csm.VERIFIER_ROLE() - # - # return [ - # { - # "target": AGENT, - # "value": 0, - # "data": agent_forward( - # [ - # ( - # dg_item_address_1, dg_item_encoded_input_1, - # ) - # ] - # )[1], - # }, - # ] - pass + # TODO Create all the dual governance calls that match the voting script + dg_items = [ + # # TODO 1.1. DG voting item 1 description + # agent_forward([ + # (dg_item_address_1, dg_item_encoded_input_1) + # ]), + # # TODO 1.2. DG voting item 2 description + # agent_forward([ + # (dg_item_address_2, dg_item_encoded_input_2) + # ]), + ] + + # Convert each dg_item to the expected format + proposal_calls = [] + for dg_item in dg_items: + target, data = dg_item # agent_forward returns (target, data) + proposal_calls.append({ + "target": target, + "value": 0, + "data": data + }) + + return proposal_calls def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_governance_proposal_calls): @@ -68,6 +76,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g # ========================= Arrange variables =========================== # ======================================================================= voting = interface.Voting(VOTING) + agent = interface.Agent(AGENT) timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) @@ -114,20 +123,20 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT - assert count_vote_items_by_events(vote_tx, voting) == EXPECTED_VOTE_EVENTS_COUNT + assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT if EXPECTED_DG_PROPOSAL_ID is not None: assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() - # Validate DG Proposal Submit event - validate_dual_governance_submit_event( - vote_events[0], - proposal_id=EXPECTED_DG_PROPOSAL_ID, - proposer=VOTING, - executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, - metadata="TODO DG proposal description", - proposal_calls=dual_governance_proposal_calls, - emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], - ) + # TODO Validate DG Proposal Submit event + # validate_dual_governance_submit_event( + # vote_events[0], + # proposal_id=EXPECTED_DG_PROPOSAL_ID, + # proposer=VOTING, + # executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + # metadata="TODO DG proposal description", + # proposal_calls=dual_governance_proposal_calls, + # emitted_by=[EMERGENCY_PROTECTED_TIMELOCK, DUAL_GOVERNANCE], + # ) # TODO validate all other voting events @@ -154,7 +163,7 @@ def test_vote(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, dual_g timelock=EMERGENCY_PROTECTED_TIMELOCK, admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, ) - assert count_vote_items_by_events(dg_tx, agent) == EXPECTED_DG_EVENTS_COUNT + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT # TODO validate all DG events diff --git a/tests/acceptance/test_accounting_oracle.py b/tests/acceptance/test_accounting_oracle.py index 919b48287..a0459ee6e 100644 --- a/tests/acceptance/test_accounting_oracle.py +++ b/tests/acceptance/test_accounting_oracle.py @@ -29,9 +29,7 @@ def test_proxy(contract): def test_constants(contract): - assert contract.LIDO() == contracts.lido assert contract.LOCATOR() == contracts.lido_locator - assert contract.LEGACY_ORACLE() == contracts.legacy_oracle assert contract.EXTRA_DATA_FORMAT_EMPTY() == 0 assert contract.EXTRA_DATA_FORMAT_LIST() == 1 assert contract.EXTRA_DATA_TYPE_STUCK_VALIDATORS() == 1 @@ -41,22 +39,15 @@ def test_constants(contract): def test_versioned(contract): - assert contract.getContractVersion() == 3 + assert contract.getContractVersion() == 4 def test_initialize(contract): - with reverts(encode_error("IncorrectOracleMigration(uint256)", [2])): + with reverts("NonZeroContractVersionOnInit: "): contract.initialize( contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), HASH_CONSENSUS_FOR_AO, - 1, - {"from": contracts.voting}, - ) - with reverts(encode_error("NonZeroContractVersionOnInit()")): - contract.initializeWithoutMigration( - contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), - HASH_CONSENSUS_FOR_AO, - 1, + AO_CONSENSUS_VERSION, 1, {"from": contracts.voting}, ) @@ -64,18 +55,11 @@ def test_initialize(contract): def test_petrified(contract): impl = interface.AccountingOracle(ACCOUNTING_ORACLE_IMPL) - with reverts(encode_error("IncorrectOracleMigration(uint256)", [2])): + with reverts("NonZeroContractVersionOnInit: "): impl.initialize( contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), HASH_CONSENSUS_FOR_AO, - 1, - {"from": contracts.voting}, - ) - with reverts(encode_error("NonZeroContractVersionOnInit()")): - impl.initializeWithoutMigration( - contract.getRoleMember(contract.DEFAULT_ADMIN_ROLE(), 0), - HASH_CONSENSUS_FOR_AO, - 1, + AO_CONSENSUS_VERSION, 1, {"from": contracts.voting}, ) diff --git a/tests/acceptance/test_accounting_oracle_negative.py b/tests/acceptance/test_accounting_oracle_negative.py index 2e65a9cbc..7c80a19d1 100644 --- a/tests/acceptance/test_accounting_oracle_negative.py +++ b/tests/acceptance/test_accounting_oracle_negative.py @@ -184,12 +184,12 @@ def test_setConsensusContract(accounting_oracle: Contract, aragon_agent: Account def test_finalize_upgrade(accounting_oracle: Contract, stranger: Account): with reverts(encode_error("InvalidContractVersionIncrement()")): - accounting_oracle.finalizeUpgrade_v2( + accounting_oracle.finalizeUpgrade_v4( 1, {"from": stranger}, ) with reverts(encode_error("InvalidContractVersionIncrement()")): - accounting_oracle.finalizeUpgrade_v2( + accounting_oracle.finalizeUpgrade_v4( 2, {"from": stranger}, ) @@ -353,15 +353,16 @@ def test_unexpected_extra_data_item(self, extra_data_service: ExtraDataService) (1, 5): self.get_nor_operator_exited_keys(5) + 1, (1, 6): self.get_nor_operator_exited_keys(6) + 1, (1, 7): self.get_nor_operator_exited_keys(7) + 1, - (1, 8): self.get_nor_operator_exited_keys(8) + 1, (1, 9): self.get_nor_operator_exited_keys(9) + 1, (1, 10): self.get_nor_operator_exited_keys(10) + 1, + (1, 11): self.get_nor_operator_exited_keys(11) + 1, }, MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, 1, ) with reverts( + # In case of OUT_OF_RANGE error just remove exited NO (with all validators exited) from list above ^^^ encode_error( "UnexpectedExtraDataItemsCount(uint256,uint256)", [ diff --git a/tests/acceptance/test_burner.py b/tests/acceptance/test_burner.py index 31913dde2..95234854f 100644 --- a/tests/acceptance/test_burner.py +++ b/tests/acceptance/test_burner.py @@ -15,5 +15,5 @@ def contract() -> interface.Burner: def test_links(contract): - assert contract.STETH() == contracts.lido - assert contract.TREASURY() == contracts.agent + assert contract.LIDO() == contracts.lido + assert contract.LOCATOR() == contracts.lido_locator diff --git a/tests/acceptance/test_legacy_oracle.py b/tests/acceptance/test_legacy_oracle.py deleted file mode 100644 index 49848227c..000000000 --- a/tests/acceptance/test_legacy_oracle.py +++ /dev/null @@ -1,89 +0,0 @@ -import pytest -from brownie import ZERO_ADDRESS, interface, chain, reverts # type: ignore - -from utils.config import ( - contracts, - LEGACY_ORACLE, - LEGACY_ORACLE_IMPL, - HASH_CONSENSUS_FOR_AO, - ACCOUNTING_ORACLE, - ORACLE_ARAGON_APP_ID, - ARAGON_EVMSCRIPT_REGISTRY, - CHAIN_SLOTS_PER_EPOCH, - CHAIN_SECONDS_PER_SLOT, - CHAIN_GENESIS_TIME, - AO_EPOCHS_PER_FRAME, -) - -lastSeenTotalPooledEther = 5879742251110033487920093 - -@pytest.fixture(scope="module") -def contract() -> interface.LegacyOracle: - return interface.LegacyOracle(LEGACY_ORACLE) - - -def test_links(contract): - assert contract.getLido() == contracts.lido - assert contract.getAccountingOracle() == contracts.accounting_oracle - assert contract.getEVMScriptRegistry() == ARAGON_EVMSCRIPT_REGISTRY - - -def test_aragon(contract): - proxy = interface.AppProxyUpgradeable(contract) - assert proxy.implementation() == LEGACY_ORACLE_IMPL - assert contract.kernel() == contracts.kernel - assert contract.appId() == ORACLE_ARAGON_APP_ID - assert contract.hasInitialized() == True - assert contract.isPetrified() == False - - -def test_versioned(contract): - assert contract.getContractVersion() == 4 - - -def test_initialize(contract): - with reverts("INIT_ALREADY_INITIALIZED"): - contract.initialize(contracts.lido_locator, HASH_CONSENSUS_FOR_AO, {"from": contracts.voting}) - - -def test_finalize_upgrade(contract): - with reverts("WRONG_BASE_VERSION"): - contract.finalizeUpgrade_v4(ACCOUNTING_ORACLE, {"from": contracts.voting}) - - -def test_petrified(): - impl = interface.LegacyOracle(LEGACY_ORACLE_IMPL) - with reverts("INIT_ALREADY_INITIALIZED"): - impl.initialize(contracts.lido_locator, HASH_CONSENSUS_FOR_AO, {"from": contracts.voting}) - - with reverts("WRONG_BASE_VERSION"): - impl.finalizeUpgrade_v4(ACCOUNTING_ORACLE, {"from": contracts.voting}) - - -def test_recoverability(contract): - assert contract.getRecoveryVault() == ZERO_ADDRESS - assert contract.allowRecoverability(contracts.ldo_token) == True - - -def test_legacy_oracle_state(contract): - reported_delta = contract.getLastCompletedReportDelta() - assert reported_delta["postTotalPooledEther"] > lastSeenTotalPooledEther - assert reported_delta["preTotalPooledEther"] >= lastSeenTotalPooledEther - assert reported_delta["timeElapsed"] >= 86400 - - current_frame = contract.getCurrentFrame() - assert current_frame["frameEpochId"] > 0 - assert current_frame["frameStartTime"] > 0 - assert current_frame["frameEndTime"] > 0 - - assert contract.getLastCompletedEpochId() > 0 - - assert contract.getInitializationBlock() > 0 - assert contract.getInitializationBlock() <= chain.height - - oracle_beacon_spec = contracts.legacy_oracle.getBeaconSpec() - - assert oracle_beacon_spec["epochsPerFrame"] == AO_EPOCHS_PER_FRAME - assert oracle_beacon_spec["slotsPerEpoch"] == CHAIN_SLOTS_PER_EPOCH - assert oracle_beacon_spec["secondsPerSlot"] == CHAIN_SECONDS_PER_SLOT - assert oracle_beacon_spec["genesisTime"] == CHAIN_GENESIS_TIME diff --git a/tests/acceptance/test_lido.py b/tests/acceptance/test_lido.py index 0ac485621..d348e9026 100644 --- a/tests/acceptance/test_lido.py +++ b/tests/acceptance/test_lido.py @@ -45,7 +45,7 @@ def test_pausable(contract): def test_versioned(contract): - assert contract.getContractVersion() == 2 + assert contract.getContractVersion() == 3 def test_initialize(contract): @@ -55,7 +55,7 @@ def test_initialize(contract): def test_finalize_upgrade(contract): with reverts("UNEXPECTED_CONTRACT_VERSION"): - contract.finalizeUpgrade_v2(contracts.lido_locator, contracts.eip712_steth, {"from": contracts.voting}) + contract.finalizeUpgrade_v3(contracts.burner, [contracts.eip712_steth], 0, {"from": contracts.voting}) def test_petrified(): @@ -63,8 +63,11 @@ def test_petrified(): with reverts("INIT_ALREADY_INITIALIZED"): impl.initialize(contracts.lido_locator, contracts.eip712_steth, {"from": contracts.voting}) - with reverts("UNEXPECTED_CONTRACT_VERSION"): - impl.finalizeUpgrade_v2(contracts.lido_locator, contracts.eip712_steth, {"from": contracts.voting}) + # For petrified implementation, hasInitialized() returns false because + # AragonApp (LIDO) sets initializationBlock to PETRIFIED_BLOCK = uint256(-1) + # and hasInitialized() requires getBlockNumber() >= initializationBlock. + with reverts("NOT_INITIALIZED"): + impl.finalizeUpgrade_v3(contracts.burner, [contracts.eip712_steth], 0, {"from": contracts.voting}) def test_links(contract): @@ -92,7 +95,7 @@ def test_lido_state(contract): for module in modules ) - assert stake_limit["isStakingPaused"] == False + assert stake_limit["isStakingPaused_"] == False assert stake_limit["isStakingLimitSet"] == True assert stake_limit["maxStakeLimit"] == LIDO_MAX_STAKE_LIMIT_ETH * ONE_ETH diff --git a/tests/acceptance/test_locator.py b/tests/acceptance/test_locator.py index e4e22a68a..b39619003 100644 --- a/tests/acceptance/test_locator.py +++ b/tests/acceptance/test_locator.py @@ -19,7 +19,6 @@ def test_addresses(contract): assert contract.accountingOracle() == contracts.accounting_oracle assert contract.depositSecurityModule() == contracts.deposit_security_module assert contract.elRewardsVault() == contracts.execution_layer_rewards_vault - assert contract.legacyOracle() == contracts.legacy_oracle assert contract.lido() == contracts.lido assert contract.oracleReportSanityChecker() == contracts.oracle_report_sanity_checker assert contract.postTokenRebaseReceiver() == contracts.token_rate_notifier @@ -40,12 +39,12 @@ def test_addresses(contract): contracts.withdrawal_vault, ) - assert contract.oracleReportComponentsForLido() == ( + assert contract.oracleReportComponents() == ( contracts.accounting_oracle, - contracts.execution_layer_rewards_vault, contracts.oracle_report_sanity_checker, contracts.burner, contracts.withdrawal_queue, - contracts.withdrawal_vault, contracts.token_rate_notifier, + contracts.staking_router, + contracts.vault_hub, ) diff --git a/tests/acceptance/test_node_operators_registry.py b/tests/acceptance/test_node_operators_registry.py index aea5d7069..c9f4ac0d0 100644 --- a/tests/acceptance/test_node_operators_registry.py +++ b/tests/acceptance/test_node_operators_registry.py @@ -114,8 +114,9 @@ def test_nor_state(contract): assert node_operator["totalVettedValidators"] <= node_operator["totalAddedValidators"] node_operator_summary = contract.getNodeOperatorSummary(id) - exited_node_operators = [12, 1] # NO id 12 was added on vote 23-05-23, NO id 1 was added on vote 03-10-23 - assert node_operator_summary["targetLimitMode"] == (1 if id in exited_node_operators else 0) + exited_node_operators = [12, 1] # NO id 12 was added on vote 23-05-23, NO id 1 was added on vote 03-10-23 + soft_limit_0_node_operators = [32] # NO id 32 was added on vote 10-12-25 + assert node_operator_summary["targetLimitMode"] == (1 if id in exited_node_operators or id in soft_limit_0_node_operators else 0) assert node_operator_summary["targetValidatorsCount"] == 0 # Can be more than 0 in regular protocol operations # assert node_operator_summary["stuckValidatorsCount"] == 0 @@ -133,11 +134,13 @@ def test_nor_state(contract): assert node_operator["totalExitedValidators"] == node_operator_summary["totalExitedValidators"] assert node_operator["totalDepositedValidators"] == node_operator_summary["totalDepositedValidators"] - no_depositable_validators_count = ( - node_operator["totalVettedValidators"] - node_operator["totalDepositedValidators"] - ) - - assert node_operator_summary["depositableValidatorsCount"] == no_depositable_validators_count + if id in soft_limit_0_node_operators: + assert node_operator_summary["depositableValidatorsCount"] == 0 + else: + no_depositable_validators_count = ( + node_operator["totalVettedValidators"] - node_operator["totalDepositedValidators"] + ) + assert node_operator_summary["depositableValidatorsCount"] == no_depositable_validators_count def _str_to_bytes32(s: str) -> str: return "0x{:0<64}".format(s.encode("utf-8").hex()) diff --git a/tests/conftest.py b/tests/conftest.py index 6453db18d..f14b9cb04 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -129,9 +129,8 @@ def execute_votes(accounts, vote_ids, dao_voting, topup="10 ether"): dao_voting.vote(vote_id, True, False, {"from": account}) # wait for the vote to end - time_to_end = dao_voting.getVote(vote_id)["startDate"] + get_vote_duration() - chain.time() - if time_to_end > 0: - chain.sleep(time_to_end) + # time_to_end = dao_voting.getVote(vote_id)["startDate"] + get_vote_duration() - chain.time() + chain.sleep(get_vote_duration()) chain.mine() for vote_id in vote_ids: @@ -236,7 +235,6 @@ def parse_events_from_local_abi(): HASH_CONSENSUS_FOR_AO, HASH_CONSENSUS_FOR_VEBO, ], - "LegacyOracle": [LEGACY_ORACLE, LEGACY_ORACLE_IMPL], "Lido": [LIDO, LIDO_IMPL], "LidoLocator": [LIDO_LOCATOR], "LidoExecutionLayerRewardsVault": [EXECUTION_LAYER_REWARDS_VAULT], diff --git a/tests/regression/test_csm.py b/tests/regression/test_csm.py index a16bcf854..151c626b2 100644 --- a/tests/regression/test_csm.py +++ b/tests/regression/test_csm.py @@ -88,29 +88,45 @@ def ejector(): def strikes(): return contracts.cs_strikes +@pytest.fixture(params=[pytest.param(1, id="1 key")]) +def keys_count(request): + return request.param @pytest.fixture -def depositable_node_operator(csm, accounting, permissionless_gate, stranger): +def depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger): + return _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger) + + +def _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger): increase_staking_module_share(module_id=CSM_MODULE_ID, share_multiplier=2) csm.cleanDepositQueue(2 * csm.getNonce(), {"from": stranger.address}) + keys_to_deposit = 0 for queue_priority in range(0, 6): deposit_batch = csm.depositQueueItem(queue_priority, csm.depositQueuePointers(queue_priority)["head"]) if deposit_batch: node_operator_id = (deposit_batch >> 192) & ((1 << 64) - 1) - keys_count = (deposit_batch >> 128) & ((1 << 64) - 1) - break + batch_keys_count = (deposit_batch >> 128) & ((1 << 64) - 1) + if batch_keys_count >= keys_count: + break else: address = accounts[7].address - keys_count = 5 node_operator_id = csm_add_node_operator(csm, permissionless_gate, accounting, address, keys_count=keys_count) - return node_operator_id, keys_count + return node_operator_id, keys_to_deposit -@pytest.fixture -def node_operator(depositable_node_operator, csm, accounting) -> int: - node_operator, keys_count = depositable_node_operator - fill_deposit_buffer(keys_count) - contracts.lido.deposit(keys_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) +@pytest.fixture() +def node_operator(keys_count, csm, accounting, permissionless_gate, stranger) -> int: + total_node_operators = contracts.csm.getNodeOperatorsCount() + for no_id in range(0, total_node_operators): + no = csm.getNodeOperator(no_id) + if no["totalDepositedKeys"] - no["totalWithdrawnKeys"] >= keys_count: + return no_id + + # Fallback: use _depositable_node_operator + node_operator, required_deposits = _depositable_node_operator(keys_count, csm, accounting, permissionless_gate, stranger) + to_deposit = required_deposits + keys_count + fill_deposit_buffer(to_deposit) + contracts.lido.deposit(to_deposit, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) return node_operator @@ -194,11 +210,12 @@ def test_add_node_operator_permissionless(csm, permissionless_gate, accounting, @pytest.mark.usefixtures("pause_modules") -def test_deposit(depositable_node_operator, csm, remove_stake_limit): - (node_operator, keys_count) = depositable_node_operator - fill_deposit_buffer(keys_count) +def test_deposit(depositable_node_operator, csm, remove_stake_limit, keys_count): + (node_operator, required_deposits) = depositable_node_operator + to_deposit = required_deposits + keys_count + fill_deposit_buffer(to_deposit) total_deposited_before = csm.getNodeOperator(node_operator)["totalDepositedKeys"] - contracts.lido.deposit(keys_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) + contracts.lido.deposit(to_deposit, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) no = csm.getNodeOperator(node_operator) assert no["totalDepositedKeys"] == total_deposited_before + keys_count @@ -229,7 +246,8 @@ def test_csm_target_limits(csm, node_operator): def test_csm_report_exited(csm, node_operator, extra_data_service): total_exited = csm.getStakingModuleSummary()["totalExitedValidators"] - exited_keys = 5 + no = csm.getNodeOperator(node_operator) + exited_keys = no["totalExitedKeys"] + 1 extra_data = extra_data_service.collect({(CSM_MODULE_ID, node_operator): exited_keys}, exited_keys, exited_keys) oracle_report( extraDataFormat=1, @@ -244,13 +262,13 @@ def test_csm_report_exited(csm, node_operator, extra_data_service): assert no["totalExitedKeys"] == exited_keys -def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_data_service, remove_stake_limit): - (exited_before, deposited_before, depositable_before) = contracts.staking_router.getStakingModuleSummary( - CSM_MODULE_ID - ) +@pytest.mark.usefixtures("pause_modules") +def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_data_service, remove_stake_limit, permissionless_gate, stranger): # Assure there are new exited keys - exited_keys = 5 + (exited_before, _, _) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) + no = csm.getNodeOperator(node_operator) + exited_keys = no["totalExitedKeys"] + 1 extra_data = extra_data_service.collect({(CSM_MODULE_ID, node_operator): exited_keys}, exited_keys, exited_keys) oracle_report( extraDataFormat=1, @@ -262,20 +280,23 @@ def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_da ) # Assure there are new deposited keys + keys_to_deposit = 2 + depositable_no, required_deposits = _depositable_node_operator(keys_to_deposit, csm, accounting, permissionless_gate, stranger) + (_, deposited_before, _) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) + to_deposit = required_deposits + keys_to_deposit + fill_deposit_buffer(to_deposit) + contracts.lido.deposit(to_deposit, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) - deposits_count = 3 - new_keys = 5 - new_depositable = new_keys - deposits_count - csm_upload_keys(csm, accounting, node_operator, new_keys) - increase_staking_module_share(module_id=CSM_MODULE_ID, share_multiplier=2) - fill_deposit_buffer(deposits_count) - contracts.lido.deposit(deposits_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) + # Assure there are new depositable keys + (_, _, depositable_before) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) + new_depositable = 5 + csm_upload_keys(csm, accounting, node_operator, new_depositable) (exited_after, deposited_after, depositable_after) = contracts.staking_router.getStakingModuleSummary(CSM_MODULE_ID) assert exited_after == exited_before + exited_keys - assert deposited_after == deposited_before + deposits_count + assert deposited_after == deposited_before + to_deposit assert depositable_after == depositable_before + new_depositable @@ -309,7 +330,9 @@ def test_csm_get_node_operator_summary(csm, node_operator, extra_data_service): def test_csm_decrease_vetted_keys(csm, depositable_node_operator, stranger): - (node_operator, keys_count) = depositable_node_operator + (node_operator, _) = depositable_node_operator + no = csm.getNodeOperator(node_operator) + depositable_keys = no["depositableValidatorsCount"] - no["totalWithdrawnKeys"] total_added_keys = csm.getNodeOperator(node_operator)["totalAddedKeys"] block_number = web3.eth.get_block_number() block = web3.eth.get_block(block_number) @@ -320,7 +343,7 @@ def test_csm_decrease_vetted_keys(csm, depositable_node_operator, stranger): staking_module_id=CSM_MODULE_ID, nonce=staking_module_nonce, node_operator_ids=to_bytes(node_operator, 16), - vetted_signing_keys_counts=to_bytes(total_added_keys - keys_count, 32), + vetted_signing_keys_counts=to_bytes(total_added_keys - depositable_keys, 32), ) set_single_guardian(contracts.deposit_security_module, contracts.agent, stranger) @@ -328,7 +351,7 @@ def test_csm_decrease_vetted_keys(csm, depositable_node_operator, stranger): contracts.deposit_security_module.unvetSigningKeys(*unvet_args.to_tuple(), (0, 0), {"from": stranger.address}) no = csm.getNodeOperator(node_operator) - assert no["totalVettedKeys"] == total_added_keys - keys_count + assert no["totalVettedKeys"] == total_added_keys - depositable_keys def test_csm_penalize_node_operator(csm, accounting, node_operator, helpers): @@ -439,8 +462,10 @@ def test_csm_remove_key(csm, parameters_registry, accounting, node_operator): assert no["totalAddedKeys"] == keys_before - 1 -def test_eject_bad_performer(csm, accounting, ejector, strikes, node_operator, stranger): - index_to_eject = 0 +@pytest.mark.parametrize("keys_count", [pytest.param(2, id="2 keys")], indirect=True) +def test_eject_bad_performer(csm, accounting, ejector, strikes, node_operator, stranger, keys_count): + no = csm.getNodeOperator(node_operator) + index_to_eject = no["totalDepositedKeys"] - 2 pubkey_to_eject = csm.getSigningKeys(node_operator, index_to_eject, 1) leaf_to_eject = (node_operator, pubkey_to_eject, [1, 1, 1, 1, 1, 1]) another_pubkey = csm.getSigningKeys(node_operator, index_to_eject + 1, 1) @@ -484,18 +509,22 @@ def test_eject_bad_performer(csm, accounting, ejector, strikes, node_operator, s def test_voluntary_eject(csm, ejector, node_operator): eject_payment_value = get_sys_fee_to_eject() operator_address = csm.getNodeOperatorOwner(node_operator) + no = csm.getNodeOperator(node_operator) + index_to_eject = no["totalDepositedKeys"] - 1 tx = ejector.voluntaryEject( - node_operator, 0, 1, ZERO_ADDRESS, {"value": eject_payment_value, "from": operator_address} + node_operator, index_to_eject, 1, ZERO_ADDRESS, {"value": eject_payment_value, "from": operator_address} ) assert "TriggeredExitFeeRecorded" not in tx.events def test_report_validator_exit_delay(csm, accounting, parameters_registry, node_operator): - pubkey = csm.getSigningKeys(node_operator, 0, 1) day_in_seconds = 60 * 60 * 24 + no = csm.getNodeOperator(node_operator) + index_to_report = no["totalDepositedKeys"] - 1 + pubkey = csm.getSigningKeys(node_operator, index_to_report, 1) - tx = csm.reportValidatorExitDelay(node_operator, 0, pubkey, 7 * day_in_seconds, {"from": contracts.staking_router}) + tx = csm.reportValidatorExitDelay(node_operator, index_to_report, pubkey, 7 * day_in_seconds, {"from": contracts.staking_router}) assert "ValidatorExitDelayProcessed" in tx.events assert tx.events["ValidatorExitDelayProcessed"]["nodeOperatorId"] == node_operator assert tx.events["ValidatorExitDelayProcessed"]["pubkey"] == pubkey @@ -507,7 +536,9 @@ def test_report_validator_exit_delay(csm, accounting, parameters_registry, node_ def test_on_validator_exit_triggered(csm, node_operator): eject_payment_value = 1 - pubkey = csm.getSigningKeys(node_operator, 0, 1) + no = csm.getNodeOperator(node_operator) + index_to_report = no["totalDepositedKeys"] - 1 + pubkey = csm.getSigningKeys(node_operator, index_to_report, 1) exit_type = 3 tx = csm.onValidatorExitTriggered(node_operator, pubkey, 1, exit_type, {"from": contracts.staking_router}) diff --git a/tests/regression/test_gate_seal.py b/tests/regression/test_gate_seal.py index f11a3c486..98b07daba 100644 --- a/tests/regression/test_gate_seal.py +++ b/tests/regression/test_gate_seal.py @@ -1,6 +1,6 @@ import pytest -from brownie import reverts, accounts, chain, web3, Wei # type: ignore +from brownie import reverts, accounts, chain, web3, Wei, interface # type: ignore from eth_hash.auto import keccak from utils.test.exit_bus_data import LidoValidator @@ -24,6 +24,10 @@ CHAIN_SLOTS_PER_EPOCH, CHAIN_SECONDS_PER_SLOT, AO_EPOCHS_PER_FRAME, + VAULT_HUB, + PREDEPOSIT_GUARANTEE, + GATE_SEAL_V3, + RESEAL_MANAGER, ) @@ -42,6 +46,20 @@ def test_gate_seal_expiration(gate_seal_committee): contracts.gate_seal.seal([WITHDRAWAL_QUEUE], {"from": gate_seal_committee}) +def test_gate_seal_v3_expiration(gate_seal_committee): + gate_seal_v3 = interface.GateSeal(GATE_SEAL_V3) + + assert not gate_seal_v3.is_expired() + time = chain.time() + expiry = gate_seal_v3.get_expiry_timestamp() + chain.sleep(expiry - time + 1) + chain.mine(1) + + assert gate_seal_v3.is_expired() + with reverts("gate seal: expired"): + gate_seal_v3.seal(gate_seal_v3.get_sealables(), {"from": gate_seal_committee}) + + def test_gate_seal_twg_veb_expiration(gate_seal_committee): assert not contracts.veb_twg_gate_seal.is_expired() time = chain.time() @@ -298,15 +316,27 @@ def test_gate_seal_twg_veb_scenario(steth_holder, gate_seal_committee, eth_whale (_,_,_, vebInitLimit1, vebInitLimit2) = contracts.validators_exit_bus_oracle.getExitRequestLimitFullInfo() contracts.validators_exit_bus_oracle.submitExitRequestsHash(hash, {"from": "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977"}) contracts.validators_exit_bus_oracle.submitExitRequestsData((report[4], 1), {"from": submitter}) - (_,_,_, twgInitLimit1, twgInitLimit2) = contracts.triggerable_withdrawals_gateway.getExitRequestLimitFullInfo() + ( + twgInitMaxExitRequestLimit1, + twgInitExitsPerFrameLimit1, + twgInitFrameDurationInSeconds1, + twgInitPrevExitRequestsLimit1, + twgInitCurrentRequestLimit2 + ) = contracts.triggerable_withdrawals_gateway.getExitRequestLimitFullInfo() tx = contracts.validators_exit_bus_oracle.triggerExits((report[4], 1), [0], steth_holder, {"from": steth_holder, 'value': value}) - (_,_,_, twgLimit1, twgLimit2) = contracts.triggerable_withdrawals_gateway.getExitRequestLimitFullInfo() + ( + twgMaxExitRequestLimit1, + twgExitsPerFrameLimit1, + twgFrameDurationInSeconds1, + twgPrevExitRequestsLimit1, + twgCurrentRequestLimit2 + ) = contracts.triggerable_withdrawals_gateway.getExitRequestLimitFullInfo() (_,_,_, vebLimit1, vebLimit2) = contracts.validators_exit_bus_oracle.getExitRequestLimitFullInfo() assert vebLimit1 < vebInitLimit1 assert vebLimit2 < vebInitLimit2 - assert twgLimit1 < twgInitLimit1 - assert twgLimit2 < twgInitLimit2 + assert twgPrevExitRequestsLimit1 == twgCurrentRequestLimit2 + assert twgCurrentRequestLimit2 < twgInitCurrentRequestLimit2 assert len(tx.events["WithdrawalRequestAdded"]['request']) == 56 # 48 + 8 pubkey_bytes = tx.events["WithdrawalRequestAdded"]['request'][:48] _ = int.from_bytes(tx.events["WithdrawalRequestAdded"]['request'][48:], byteorder="big", signed=False) @@ -314,6 +344,61 @@ def test_gate_seal_twg_veb_scenario(steth_holder, gate_seal_committee, eth_whale pubkey_hex = "0x" + pubkey_bytes.hex() assert validator_key == pubkey_hex + +def test_gate_seal_v3_vaults_scenario(gate_seal_committee): + gate_seal_v3 = interface.GateSeal(GATE_SEAL_V3) + + assert not gate_seal_v3.is_expired() + + sealables = gate_seal_v3.get_sealables() + assert len(sealables) == 2 + assert contracts.vault_hub.address in sealables + assert contracts.predeposit_guarantee.address in sealables + + pause_duration = gate_seal_v3.get_seal_duration_seconds() + + # TODO remove this after PDG unpause + reseal_manager_account = accounts.at(RESEAL_MANAGER, force=True) + contracts.predeposit_guarantee.resume({"from": reseal_manager_account}) + + assert not contracts.vault_hub.isPaused() + assert not contracts.predeposit_guarantee.isPaused() + + seal_tx = gate_seal_v3.seal(sealables, {"from": gate_seal_committee}) + + assert seal_tx.events.count("Sealed") == len(sealables) + for i, seal_event in enumerate(seal_tx.events["Sealed"]): + assert seal_event["gate_seal"] == gate_seal_v3.address + assert seal_event["sealed_for"] == pause_duration + assert seal_event["sealed_by"] == gate_seal_committee + assert seal_event["sealable"] == sealables[i] + assert seal_event["sealed_at"] == seal_tx.timestamp + + for pause_event in seal_tx.events["Paused"]: + assert pause_event["duration"] == pause_duration + + assert gate_seal_v3.is_expired() + with reverts("gate seal: expired"): + gate_seal_v3.seal(sealables, {"from": gate_seal_committee}) + + assert contracts.vault_hub.isPaused() + assert ( + contracts.vault_hub.getResumeSinceTimestamp() + == seal_tx.timestamp + pause_duration + ) + + assert contracts.predeposit_guarantee.isPaused() + assert ( + contracts.predeposit_guarantee.getResumeSinceTimestamp() + == seal_tx.timestamp + pause_duration + ) + + chain.sleep(pause_duration + 1) + chain.mine(1) + + assert not contracts.vault_hub.isPaused() + assert not contracts.predeposit_guarantee.isPaused() + def _wait_for_next_ref_slot(): wait_to_next_available_report_time(contracts.hash_consensus_for_validators_exit_bus_oracle) ref_slot, _ = contracts.hash_consensus_for_validators_exit_bus_oracle.getCurrentFrame() diff --git a/tests/regression/test_neg_rebase_sanity_checks.py b/tests/regression/test_neg_rebase_sanity_checks.py index 5f859864b..e7209ea2e 100644 --- a/tests/regression/test_neg_rebase_sanity_checks.py +++ b/tests/regression/test_neg_rebase_sanity_checks.py @@ -87,6 +87,8 @@ def test_blocked_huge_negative_rebase(oracle_report_sanity_checker): locator = contracts.lido_locator assert oracle_report_sanity_checker.address == locator.oracleReportSanityChecker() + oracle_report() + # Advance the chain 60 days more without accounting oracle reports # The idea is to simplify the calculation of the exited validators for 18 and 54 days ago chain.sleep(60 * 24 * 60 * 60) diff --git a/tests/regression/test_oracle_report_with_notifier.py b/tests/regression/test_oracle_report_with_notifier.py index 95347d691..5daabf7a2 100644 --- a/tests/regression/test_oracle_report_with_notifier.py +++ b/tests/regression/test_oracle_report_with_notifier.py @@ -1,16 +1,22 @@ import pytest from brownie import Contract, accounts, chain, interface, OpStackTokenRatePusherWithSomeErrorStub, web3, reverts from utils.test.oracle_report_helpers import oracle_report -from utils.config import contracts, get_deployer_account, network_name +from utils.config import ( + contracts, + get_deployer_account, + network_name, + L1_TOKEN_RATE_NOTIFIER, + WSTETH_TOKEN, + ACCOUNTING_ORACLE, + L1_OPTIMISM_CROSS_DOMAIN_MESSENGER, + L2_OPTIMISM_TOKEN_RATE_ORACLE, +) from utils.test.helpers import ZERO_ADDRESS, eth_balance from utils.evm_script import encode_error from typing import TypedDict, TypeVar, Any -WST_ETH = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" -ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" -L1_TOKEN_RATE_NOTIFIER = "0xe6793B9e4FbA7DE0ee833F9D02bba7DB5EB27823" -L1_CROSS_DOMAIN_MESSENGER = "0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1" -L2_TOKEN_RATE_ORACLE = "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" +# Use as mock for L2 TokenRateOracle +L2_TOKEN_RATE_ORACLE = WSTETH_TOKEN @pytest.fixture(scope="module") @@ -37,13 +43,31 @@ def test_oracle_report_revert(): """Test oracle report reverts when messenger is empty""" interface.TokenRateNotifier(L1_TOKEN_RATE_NOTIFIER) # load TokenRateNotifier contract ABI to catch correct error - web3.provider.make_request("hardhat_setCode", [L1_CROSS_DOMAIN_MESSENGER, "0x"]) - web3.provider.make_request("evm_setAccountCode", [L1_CROSS_DOMAIN_MESSENGER, "0x"]) + web3.provider.make_request("hardhat_setCode", [L1_OPTIMISM_CROSS_DOMAIN_MESSENGER, "0x"]) + web3.provider.make_request("evm_setAccountCode", [L1_OPTIMISM_CROSS_DOMAIN_MESSENGER, "0x"]) with reverts(encode_error("ErrorTokenRateNotifierRevertedWithNoData()")): oracle_report(cl_diff=0, report_el_vault=True, report_withdrawals_vault=False) +def test_only_accounting_can_call_handle_post_token_rebase(): + """Test that only Accounting can call TokenRateNotifier.handlePostTokenRebase""" + + # Any non-Accounting address should not be allowed to call handlePostTokenRebase + # Use some sane values for the call; the exact numbers are irrelevant, it should revert on caller + with reverts(encode_error("ErrorNotAuthorizedRebaseCaller()")): + contracts.token_rate_notifier.handlePostTokenRebase( + 0, # report_timestamp, + 0, # time_elapsed, + 0, # pre_total_shares, + 0, # pre_total_ether, + 0, # post_total_shares, + 0, # post_total_ether, + 0, # shares_minted_as_fees, + {"from": accounts[0]}, + ) + + def test_oracle_report_pushes_rate(): """Test oracle report emits cross domain messenger event""" @@ -55,7 +79,7 @@ def test_oracle_report_pushes_rate(): tokenRateOracle = interface.ITokenRateUpdatable(L2_TOKEN_RATE_ORACLE) - wstETH = interface.WstETH(WST_ETH) + wstETH = interface.WstETH(WSTETH_TOKEN) accountingOracle = interface.AccountingOracle(ACCOUNTING_ORACLE) tokenRate = wstETH.getStETHByWstETH(10**27) diff --git a/tests/regression/test_pause_resume.py b/tests/regression/test_pause_resume.py index 36b21b978..902223eca 100644 --- a/tests/regression/test_pause_resume.py +++ b/tests/regression/test_pause_resume.py @@ -7,6 +7,7 @@ from utils.config import contracts from utils.evm_script import encode_error from utils.import_current_votes import is_there_any_vote_scripts, start_and_execute_votes +from utils.staking_module import calc_module_reward_shares from utils.test.oracle_report_helpers import oracle_report, prepare_exit_bus_report from utils.test.helpers import almostEqEth, almostEqWithDiff @@ -135,11 +136,6 @@ def test_revert_second_stop_resume(self): with brownie.reverts("CONTRACT_IS_ACTIVE"): contracts.lido.resume({"from": contracts.agent}) - @pytest.mark.skip( - reason="Second call of pause/resume staking is not reverted right now." - "It maybe should be fixed in the future to be consistent, " - "there's not a real problem with it." - ) def test_revert_second_pause_resume_staking(self): contracts.lido.pauseStaking({"from": contracts.agent}) @@ -148,7 +144,7 @@ def test_revert_second_pause_resume_staking(self): contracts.lido.resumeStaking({"from": contracts.agent}) - with brownie.reverts(""): + with brownie.reverts("ALREADY_RESUMED"): contracts.lido.resumeStaking({"from": contracts.agent}) def test_revert_second_stop_staking_module(self, helpers, stranger): @@ -218,18 +214,15 @@ def test_stopped_lido_cant_deposit(): contracts.lido.deposit(1, 1, "0x", {"from": contracts.deposit_security_module}), -@pytest.mark.usefixtures("stopped_lido") def test_resumed_staking_can_stake(stranger): + contracts.lido.pauseStaking({"from": contracts.agent}) contracts.lido.resumeStaking({"from": contracts.agent}) stranger.transfer(contracts.lido, DEPOSIT_AMOUNT) - @pytest.mark.usefixtures("stopped_lido") -def test_resumed_staking_cant_deposit(): - contracts.lido.resumeStaking({"from": contracts.agent}) - - with brownie.reverts("CAN_NOT_DEPOSIT"): - contracts.lido.deposit(1, 1, "0x", {"from": contracts.deposit_security_module}), +def test_cant_resume_staking_on_stopped_lido(): + with brownie.reverts("CONTRACT_IS_STOPPED"): + contracts.lido.resumeStaking({"from": contracts.agent}), @pytest.mark.usefixtures("stopped_lido") @@ -275,25 +268,85 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): contracts.staking_router.setStakingModuleStatus(1, StakingModuleStatus.DepositsPaused, {"from": stranger}) (report_tx, _) = oracle_report() - print(report_tx.events["Transfer"]) - module_index = 0 - simple_dvt_index = 1 - csm_index = 2 + # print(report_tx.events["TransferShares"]) - if report_tx.events["Transfer"][module_index]["to"] == burner.address: + # zero index - mint to accounting contract, 1 index - module, 2 index - simple dvt, 3 index - csm + module_index = 1 + simple_dvt_index = 2 + csm_index = 3 + + if report_tx.events["TransferShares"][module_index-1]["to"] == burner.address: module_index += 1 simple_dvt_index += 1 csm_index += 1 agent_index = module_index + 3 + assert report_tx.events["TransferShares"][module_index]["to"] == module_address + assert report_tx.events["TransferShares"][module_index]["from"] == contracts.accounting.address + assert report_tx.events["TransferShares"][simple_dvt_index]["to"] == contracts.simple_dvt.address + assert report_tx.events["TransferShares"][simple_dvt_index]["from"] == contracts.accounting.address + assert report_tx.events["TransferShares"][csm_index]["to"] == contracts.csm.address + assert report_tx.events["TransferShares"][csm_index]["from"] == contracts.accounting.address + assert report_tx.events["TransferShares"][agent_index]["to"] == contracts.agent + assert report_tx.events["TransferShares"][agent_index]["from"] == contracts.accounting.address + + + # the staking modules ids starts from 1 + module_stats = contracts.staking_router.getStakingModule(1) + # module_treasury_fee = module_share / share_pct * treasury_pct + module_treasury_fee = ( + report_tx.events["TransferShares"][module_index]["sharesValue"] + * 100_00 + // module_stats["stakingModuleFee"] + * module_stats["treasuryFee"] + // 100_00 + ) + simple_dvt_stats = contracts.staking_router.getStakingModule(2) + simple_dvt_treasury_fee = ( + report_tx.events["TransferShares"][simple_dvt_index]["sharesValue"] + * 100_00 + // simple_dvt_stats["stakingModuleFee"] + * simple_dvt_stats["treasuryFee"] + // 100_00 + ) + csm_stats = contracts.staking_router.getStakingModule(3) + csm_treasury_fee = ( + report_tx.events["TransferShares"][csm_index]["sharesValue"] + * 100_00 + // csm_stats["stakingModuleFee"] + * csm_stats["treasuryFee"] + // 100_00 + ) + + assert almostEqWithDiff( + module_treasury_fee + simple_dvt_treasury_fee + csm_treasury_fee, + report_tx.events["TransferShares"][agent_index]["sharesValue"], + 100, + ) + assert report_tx.events["TransferShares"][module_index]["sharesValue"] > 0 + assert report_tx.events["TransferShares"][simple_dvt_index]["sharesValue"] > 0 + assert report_tx.events["TransferShares"][csm_index]["sharesValue"] > 0 + + # do the same checks for Transfer event ------------------------------------------------------- + assert report_tx.events["Transfer"][module_index]["to"] == module_address - assert report_tx.events["Transfer"][module_index]["from"] == ZERO_ADDRESS + assert report_tx.events["Transfer"][module_index]["from"] == contracts.accounting.address + assert report_tx.events["Transfer"][simple_dvt_index]["to"] == contracts.simple_dvt.address + assert report_tx.events["Transfer"][simple_dvt_index]["from"] == contracts.accounting.address + assert report_tx.events["Transfer"][csm_index]["to"] == contracts.csm.address + assert report_tx.events["Transfer"][csm_index]["from"] == contracts.accounting.address assert report_tx.events["Transfer"][agent_index]["to"] == contracts.agent - assert report_tx.events["Transfer"][agent_index]["from"] == ZERO_ADDRESS + assert report_tx.events["Transfer"][agent_index]["from"] == contracts.accounting.address - # the staking modules ids starts from 1, so SDVT has id = 2 + # module_treasury_fee = module_share / share_pct * treasury_pct + module_treasury_fee = ( + report_tx.events["Transfer"][module_index]["value"] + * 100_00 + // module_stats["stakingModuleFee"] + * module_stats["treasuryFee"] + // 100_00 + ) simple_dvt_stats = contracts.staking_router.getStakingModule(2) - # simple_dvt_treasury_fee = sdvt_share / share_pct * treasury_pct simple_dvt_treasury_fee = ( report_tx.events["Transfer"][simple_dvt_index]["value"] * 100_00 @@ -309,12 +362,15 @@ def test_paused_staking_module_can_reward(burner: Contract, stranger): * csm_stats["treasuryFee"] // 100_00 ) + assert almostEqWithDiff( - report_tx.events["Transfer"][module_index]["value"] + simple_dvt_treasury_fee + csm_treasury_fee, + module_treasury_fee + simple_dvt_treasury_fee + csm_treasury_fee, report_tx.events["Transfer"][agent_index]["value"], 100, ) assert report_tx.events["Transfer"][module_index]["value"] > 0 + assert report_tx.events["Transfer"][simple_dvt_index]["value"] > 0 + assert report_tx.events["Transfer"][csm_index]["value"] > 0 def test_stopped_staking_module_cant_stake(stranger): diff --git a/tests/regression/test_permissions.py b/tests/regression/test_permissions.py index 8e27dabf3..c32595189 100644 --- a/tests/regression/test_permissions.py +++ b/tests/regression/test_permissions.py @@ -63,7 +63,16 @@ L1_EMERGENCY_BRAKES_MULTISIG, DUAL_GOVERNANCE_EXECUTORS, RESEAL_MANAGER, - INSURANCE_FUND + INSURANCE_FUND, + VAULT_HUB, + OPERATOR_GRID, + LAZY_ORACLE, + ACCOUNTING, + PREDEPOSIT_GUARANTEE, + VAULTS_ADAPTER, + GATE_SEAL_V3, + L1_TOKEN_RATE_NOTIFIER, + STAKING_VAULT_BEACON ) @@ -83,8 +92,8 @@ def protocol_permissions(): "type": "CustomApp", "roles": { "DEFAULT_ADMIN_ROLE": [contracts.agent], - "REQUEST_BURN_MY_STETH_ROLE": [contracts.agent], - "REQUEST_BURN_SHARES_ROLE": [contracts.lido, contracts.node_operators_registry, contracts.simple_dvt, contracts.csm.accounting()], + "REQUEST_BURN_MY_STETH_ROLE": [], + "REQUEST_BURN_SHARES_ROLE": [contracts.accounting, contracts.csm.accounting()], }, }, STAKING_ROUTER: { @@ -99,7 +108,7 @@ def protocol_permissions(): "STAKING_MODULE_MANAGE_ROLE": [contracts.agent], "REPORT_EXITED_VALIDATORS_ROLE": [contracts.accounting_oracle], "UNSAFE_SET_EXITED_VALIDATORS_ROLE": [], - "REPORT_REWARDS_MINTED_ROLE": [contracts.lido], + "REPORT_REWARDS_MINTED_ROLE": [contracts.accounting], "REPORT_VALIDATOR_EXITING_STATUS_ROLE": [contracts.validator_exit_verifier], "REPORT_VALIDATOR_EXIT_TRIGGERED_ROLE": [contracts.triggerable_withdrawals_gateway], }, @@ -494,7 +503,92 @@ def protocol_permissions(): "PAUSE_ROLE": [contracts.voting, L1_EMERGENCY_BRAKES_MULTISIG], "UNPAUSE_ROLE": [contracts.voting], }, - } + }, + VAULT_HUB: { + "contract_name": "VaultHub", + "contract": contracts.vault_hub, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": { + "DEFAULT_ADMIN_ROLE": [contracts.agent], + "VAULT_MASTER_ROLE": [], + "REDEMPTION_MASTER_ROLE": [], + "VALIDATOR_EXIT_ROLE": [VAULTS_ADAPTER], + "BAD_DEBT_MASTER_ROLE": [VAULTS_ADAPTER], + "PAUSE_ROLE": [GATE_SEAL_V3, RESEAL_MANAGER], + "RESUME_ROLE": [RESEAL_MANAGER], + }, + "role_preimages": { + "VAULT_MASTER_ROLE": "vaults.VaultHub.VaultMasterRole", + "REDEMPTION_MASTER_ROLE": "vaults.VaultHub.RedemptionMasterRole", + "VALIDATOR_EXIT_ROLE": "vaults.VaultHub.ValidatorExitRole", + "BAD_DEBT_MASTER_ROLE": "vaults.VaultHub.BadDebtMasterRole", + "PAUSE_ROLE": "PausableUntilWithRoles.PauseRole", + "RESUME_ROLE": "PausableUntilWithRoles.ResumeRole", + }, + }, + OPERATOR_GRID: { + "contract_name": "OperatorGrid", + "contract": contracts.operator_grid, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": { + "DEFAULT_ADMIN_ROLE": [contracts.agent], + "REGISTRY_ROLE": [EASYTRACK_EVMSCRIPT_EXECUTOR, VAULTS_ADAPTER], + }, + "role_preimages": { + "REGISTRY_ROLE": "vaults.OperatorsGrid.Registry", + }, + }, + LAZY_ORACLE: { + "contract_name": "LazyOracle", + "contract": contracts.lazy_oracle, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": { + "DEFAULT_ADMIN_ROLE": [contracts.agent], + "UPDATE_SANITY_PARAMS_ROLE": [], + }, + "role_preimages": { + "UPDATE_SANITY_PARAMS_ROLE": "vaults.LazyOracle.UpdateSanityParams", + }, + }, + ACCOUNTING: { + "contract_name": "Accounting", + "contract": contracts.accounting, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": {}, + }, + PREDEPOSIT_GUARANTEE: { + "contract_name": "PredepositGuarantee", + "contract": contracts.predeposit_guarantee, + "type": "CustomApp", + "proxy_owner": contracts.agent, + "roles": { + "DEFAULT_ADMIN_ROLE": [contracts.agent], + "PAUSE_ROLE": [GATE_SEAL_V3, RESEAL_MANAGER], + "RESUME_ROLE": [RESEAL_MANAGER], + }, + "role_preimages": { + "PAUSE_ROLE": "PausableUntilWithRoles.PauseRole", + "RESUME_ROLE": "PausableUntilWithRoles.ResumeRole", + }, + }, + L1_TOKEN_RATE_NOTIFIER: { + "contract_name": "TokenRateNotifier", + "contract": contracts.token_rate_notifier, + "type": "CustomApp", + "state": {"owner": contracts.agent}, + "roles": {}, + }, + STAKING_VAULT_BEACON: { + "contract_name": "UpgradeableBeacon", + "contract": contracts.staking_vault_beacon, + "type": "CustomApp", + "state": {"owner": contracts.agent}, + "roles": {}, + }, } @@ -559,7 +653,12 @@ def test_protocol_permissions(protocol_permissions): ) for role, holders in permissions_config["roles"].items(): - role_keccak = web3.keccak(text=role).hex() if role != "DEFAULT_ADMIN_ROLE" else ZERO_BYTES32.hex() + # Use custom preimage if specified, otherwise use role name + role_preimage = role + if "role_preimages" in permissions_config and role in permissions_config["role_preimages"]: + role_preimage = permissions_config["role_preimages"][role] + + role_keccak = web3.keccak(text=role_preimage).hex() if role != "DEFAULT_ADMIN_ROLE" else ZERO_BYTES32.hex() role_signature = permissions_config["contract"].signatures[role] assert permissions_config["contract"].get_method_object(role_signature)() == role_keccak @@ -623,6 +722,17 @@ def get_http_w3_provider_url(): assert False, 'Web3 HTTP Provider token env var not found' +def get_http_provider_timeout(): + """ + HTTP provider timeout in seconds for remote RPC calls. + Can be overridden via WEB3_HTTP_PROVIDER_TIMEOUT env var. + """ + if os.getenv("WEB3_HTTP_PROVIDER_TIMEOUT") is not None: + return float(os.getenv("WEB3_HTTP_PROVIDER_TIMEOUT")) + # use higher default than requests' 10s to reduce flaky ReadTimeouts in CI + return 60.0 + + def get_max_log_range(): if os.getenv("MAX_GET_LOGS_RANGE") is not None: return int(os.getenv("MAX_GET_LOGS_RANGE")) @@ -631,7 +741,12 @@ def get_max_log_range(): def active_aragon_roles(protocol_permissions): local_rpc_provider = web3 - remote_rpc_provider = Web3(Web3.HTTPProvider(get_http_w3_provider_url())) + remote_rpc_provider = Web3( + Web3.HTTPProvider( + get_http_w3_provider_url(), + request_kwargs={"timeout": get_http_provider_timeout()}, + ) + ) max_range = get_max_log_range() event_signature_hash = remote_rpc_provider.keccak(text="SetPermission(address,address,bytes32,bool)").hex() diff --git a/tests/regression/test_sanity_checks.py b/tests/regression/test_sanity_checks.py index 3314d6c6a..719d74cdc 100644 --- a/tests/regression/test_sanity_checks.py +++ b/tests/regression/test_sanity_checks.py @@ -41,12 +41,14 @@ def first_report(): def test_cant_report_more_validators_than_deposited(): (deposited, clValidators, _) = contracts.lido.getBeaconStat() - with reverts("REPORTED_MORE_DEPOSITED"): + with reverts("IncorrectReportValidators: " + str(deposited + 1) + ", " + str(clValidators) + ", " + str(deposited)): oracle_report(cl_appeared_validators=deposited - clValidators + 1, skip_withdrawals=True, silent=True) def test_validators_cant_decrease(): - with reverts("REPORTED_LESS_VALIDATORS"): + # panic code 0x11 (Arithmetic overflow) + # Brownie sometimes fails to decode panic codes and throws AttributeError + with pytest.raises((Exception, AttributeError)): oracle_report(cl_appeared_validators=-1, skip_withdrawals=True, silent=True) @@ -212,11 +214,16 @@ def test_accounting_oracle_too_much_extra_data(extra_data_service): nor_module_id = 1 nor_operators_count = contracts.node_operators_registry.getNodeOperatorsCount() i = 0 - while len(operators) < item_count and i < nor_operators_count: - (active, _, _, _, total_exited_validators_count, _, _) = contracts.node_operators_registry.getNodeOperator(i, True) - if(active): - operators[(nor_module_id, i)] = total_exited_validators_count + 1 - i = i + 1 + + for no_id in range(nor_operators_count): + (active, _, _, _, total_exited_validators_count, _, total_deposited_validators_count) = contracts.node_operators_registry.getNodeOperator(no_id, True) + + if active and total_exited_validators_count != total_deposited_validators_count: + operators[(nor_module_id, no_id)] = total_exited_validators_count + 1 + i += 1 + + if i == item_count: + break extra_data = extra_data_service.collect(operators, item_count, 1) @@ -234,20 +241,39 @@ def test_accounting_oracle_too_much_extra_data(extra_data_service): ) -@pytest.mark.skip("ganache throws 'RPCRequestError: Invalid string length' on such long extra data") def test_accounting_oracle_too_node_ops_per_extra_data_item(extra_data_service): - item_count = MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM * 10 - extra_data = extra_data_service.collect({(1, i): i for i in range(item_count)}, {}, 1, item_count) + node_ops_count = MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM + 1 + + # Collect real operators with their current exited validators count + operators = {} + nor_module_id = 1 + nor_operators_count = contracts.node_operators_registry.getNodeOperatorsCount() + i = 0 + + for no_id in range(nor_operators_count): + (active, _, _, _, total_exited_validators_count, _, total_deposited_validators_count) = contracts.node_operators_registry.getNodeOperator(no_id, True) + + if active and total_exited_validators_count != total_deposited_validators_count: + operators[(nor_module_id, no_id)] = total_exited_validators_count + 1 + i += 1 + + if i == node_ops_count: + break + + # Create extra data with too many node operators in a single item + # by setting max_no_in_payload_count to a large value + extra_data = extra_data_service.collect(operators, 1, node_ops_count) + with reverts( encode_error( "TooManyNodeOpsPerExtraDataItem(uint256,uint256)", - [MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, item_count], + [0, node_ops_count], # itemIndex=0, nodeOpsCount=25 ) ): oracle_report( extraDataFormat=1, extraDataHashList=extra_data.extra_data_hash_list, - extraDataItemsCount=1, + extraDataItemsCount=extra_data.items_count, extraDataList=extra_data.extra_data_list, ) diff --git a/tests/regression/test_staking_module_happy_path.py b/tests/regression/test_staking_module_happy_path.py index 375bf74a9..9807e4885 100644 --- a/tests/regression/test_staking_module_happy_path.py +++ b/tests/regression/test_staking_module_happy_path.py @@ -198,10 +198,15 @@ def module_happy_path(staking_module, extra_data_service, impersonated_agent, st # - Check NOs stats # - Check Report events - # Prepare extra data + # Get current exited validators count for operators + no1_exited_before = staking_module.getNodeOperatorSummary(no1_id)["totalExitedValidators"] + no2_exited_before = staking_module.getNodeOperatorSummary(no2_id)["totalExitedValidators"] + no3_exited_before = staking_module.getNodeOperatorSummary(no3_id)["totalExitedValidators"] + + # Prepare extra data - set 5 more exited validators for each operator vals_exited_non_zero = { - node_operator_gindex(staking_module.module_id, no1_id): 5, - node_operator_gindex(staking_module.module_id, no2_id): 5, + node_operator_gindex(staking_module.module_id, no1_id): no1_exited_before + 5, + node_operator_gindex(staking_module.module_id, no2_id): no2_exited_before + 5, } extra_data = extra_data_service.collect(vals_exited_non_zero, 10, 10) @@ -247,20 +252,20 @@ def module_happy_path(staking_module, extra_data_service, impersonated_agent, st assert no2_balance_shares_after - no2_balance_shares_before == no2_rewards_after_second_report assert no3_balance_shares_after - no3_balance_shares_before == no3_rewards_after_second_report - # NO stats - assert no1_summary["totalExitedValidators"] == 5 - assert no2_summary["totalExitedValidators"] == 5 - assert no3_summary["totalExitedValidators"] == 0 + # NO stats - check that exited validators increased by 5 for no1 and no2, and stayed the same for no3 + assert no1_summary["totalExitedValidators"] == no1_exited_before + 5 + assert no2_summary["totalExitedValidators"] == no2_exited_before + 5 + assert no3_summary["totalExitedValidators"] == no3_exited_before # Events exited_signing_keys_count_events = parse_exited_signing_keys_count_changed_logs( filter_transfer_logs(extra_report_tx_list[0].logs, web3.keccak(text="ExitedSigningKeysCountChanged(uint256,uint256)")) ) assert exited_signing_keys_count_events[0]["nodeOperatorId"] == no1_id - assert exited_signing_keys_count_events[0]["exitedValidatorsCount"][0] == 5 + assert exited_signing_keys_count_events[0]["exitedValidatorsCount"][0] == no1_exited_before + 5 assert exited_signing_keys_count_events[1]["nodeOperatorId"] == no2_id - assert exited_signing_keys_count_events[1]["exitedValidatorsCount"][0] == 5 + assert exited_signing_keys_count_events[1]["exitedValidatorsCount"][0] == no2_exited_before + 5 # Deposit keys deposit_and_check_keys(staking_module, no1_id, no2_id, no3_id, 50, impersonated_agent) @@ -350,9 +355,6 @@ def module_happy_path(staking_module, extra_data_service, impersonated_agent, st assert no3_deposited_keys_before != no3_deposited_keys_after -@pytest.mark.skip( - "TODO: fix the test assumptions about the state of the chain (no exited validators, depositable ETH amount)" -) def test_node_operator_registry(impersonated_agent, stranger, helpers): nor = contracts.node_operators_registry nor.module_id = 1 diff --git a/tests/snapshot/test_dsm.py b/tests/snapshot/test_dsm.py index 6de8a2ed9..22d7521e3 100644 --- a/tests/snapshot/test_dsm.py +++ b/tests/snapshot/test_dsm.py @@ -223,7 +223,7 @@ def far_block() -> int: @pytest.fixture(scope="module") def far_ts() -> int: - return chain.time() + 14 * 24 * 60 * 60 # 14 days + return chain.time() + 21 * 24 * 60 * 60 # 21 days def _sleep_till_block(block: int, ts: int) -> None: diff --git a/tests/snapshot/test_first_slots.py b/tests/snapshot/test_first_slots.py index 390eb573d..0866aea18 100644 --- a/tests/snapshot/test_first_slots.py +++ b/tests/snapshot/test_first_slots.py @@ -49,8 +49,14 @@ def test_first_slots(sandwich_upgrade: SandwichFn): def skip_slots() -> Sequence[tuple[str, int]]: """Slots that are not checked for equality""" return [ - # reset slot in kernel - (contracts.kernel.address, 0x01), + # Adding 8 new easy track factories - steps 2-9 of V3 upgrade + (contracts.easy_track.address, 0x05), + # Set soft-mode target validators limit to 0 for operator A41 - step 1.3 of operations voting + (contracts.node_operators_registry.address, 0x01), + # Transfer MATIC from Treasury to LOL Multisig - step 7 of operations voting + (contracts.finance.address, 0x07), + # periodsLength var - step 7 of operations voting can trigger _newPeriod in Finance + (contracts.finance.address, 0x09), ] @@ -92,7 +98,6 @@ def _snap(): for contract in ( contracts.lido, contracts.node_operators_registry, - contracts.legacy_oracle, contracts.deposit_security_module, contracts.execution_layer_rewards_vault, contracts.withdrawal_vault, @@ -157,7 +162,7 @@ def far_block() -> int: @pytest.fixture(scope="module") def far_ts() -> int: - return chain.time() + 14 * 24 * 60 * 60 # 14 days + return chain.time() + 21 * 24 * 60 * 60 # 21 days def _sleep_till_block(block: int, ts: int) -> None: diff --git a/tests/snapshot/test_legacy_oracle_snapshot.py b/tests/snapshot/test_legacy_oracle_snapshot.py deleted file mode 100644 index 085739b30..000000000 --- a/tests/snapshot/test_legacy_oracle_snapshot.py +++ /dev/null @@ -1,194 +0,0 @@ -from contextlib import contextmanager -from typing import Any, Callable, Sequence, TypedDict - -import brownie -import pytest -from brownie import chain, web3, accounts -from brownie.network.account import Account -from pytest_check import check -from typing_extensions import Protocol - -from tests.conftest import Helpers -from utils.config import contracts, LDO_TOKEN -from utils.evm_script import EMPTY_CALLSCRIPT -from utils.test.governance_helpers import execute_vote_and_process_dg_proposals -from utils.test.snapshot_helpers import _chain_snapshot - -from .utils import get_slot - - -class Frame(TypedDict): - """A snapshot of the state before and after an action.""" - - snap: dict[str, Any] - func: str - - -Stack = Sequence[Frame] -SnapshotFn = Callable[[], dict] - - -class SandwichFn(Protocol): - @staticmethod - def __call__( - actions_list: Sequence[Callable], - snapshot_fn: SnapshotFn = ..., - snapshot_block: int = ..., - ) -> tuple[Stack, Stack]: - ... - - -def test_legacy_oracle_no_changes_in_views(sandwich_upgrade: SandwichFn): - """Test that no views change during the upgrade process""" - - stacks = sandwich_upgrade([]) - _stacks_equal(stacks) - - -@pytest.fixture(scope="module") -def do_snapshot( - interface, - some_contract: Account, -): - oracle = contracts.legacy_oracle - - def _snap(): - block = chain.height - res = {} - - with brownie.multicall(block_identifier=block): - res |= { - "block_number": chain.height, - "chain_time": web3.eth.get_block(chain.height)["timestamp"], - "address": oracle.address, - # AppProxyUpgradeable - "isDepositable": interface.AppProxyUpgradeable(oracle.address).isDepositable(), - # Oracle - "allowRecoverability(LDO)": oracle.allowRecoverability(LDO_TOKEN), - "allowRecoverability(SOME_CONTRACT)": oracle.allowRecoverability(some_contract), - "getBeaconSpec": oracle.getBeaconSpec(), - "getCurrentEpochId": oracle.getCurrentEpochId(), - "getCurrentFrame": oracle.getCurrentFrame(), - "getLastCompletedEpochId": oracle.getLastCompletedEpochId(), - "getLastCompletedReportDelta": oracle.getLastCompletedReportDelta(), - "getLido": oracle.getLido(), - # AragonApp - # "getRecoveryVault": oracle.getRecoveryVault(), - "kernel": oracle.kernel(), - "appId": oracle.appId(), - "getEVMScriptExecutor(nil)": oracle.getEVMScriptExecutor(EMPTY_CALLSCRIPT), - "getEVMScriptRegistry": oracle.getEVMScriptRegistry(), - "getInitializationBlock": oracle.getInitializationBlock(), - "hasInitialized": oracle.hasInitialized(), - } - - for v1_slot in ( - # LidoOracle.sol - "lido.LidoOracle.allowedBeaconBalanceAnnualRelativeIncrease", - "lido.LidoOracle.allowedBeaconBalanceDecrease", - "lido.LidoOracle.beaconReportReceiver", - "lido.LidoOracle.beaconSpec", - "lido.LidoOracle.expectedEpochId", - "lido.LidoOracle.lastCompletedEpochId", - "lido.LidoOracle.lastReportedEpochId", - "lido.LidoOracle.lido", - "lido.LidoOracle.postCompletedTotalPooledEther", - "lido.LidoOracle.preCompletedTotalPooledEther", - "lido.LidoOracle.quorum", - "lido.LidoOracle.reportsBitMask", - "lido.LidoOracle.timeElapsed", - # AragonApp.sol - "aragonOS.appStorage.kernel", - "aragonOS.appStorage.appId", - ): - res[v1_slot] = get_slot( - oracle.address, - name=v1_slot, - block=block, - ) - - res["members"] = get_slot( - oracle.address, - pos=0, - as_list=True, - block=block, - ) - - return res - - return _snap - - -@pytest.fixture(scope="module") -def some_contract(accounts) -> Account: - # Multicall3 contract deployed almost on the every network on the same address - return accounts.at("0xcA11bde05977b3631167028862bE2a173976CA11", force=True) - - -@pytest.fixture(scope="module") -def sandwich_upgrade( - do_snapshot: SnapshotFn, - far_block: int, - far_ts: int, - helpers: Helpers, - vote_ids_from_env: Any, - dg_proposal_ids_from_env: Any -) -> SandwichFn: - """Snapshot the state before and after the upgrade and return the two frames""" - - def _do( - actions_list: Sequence[Callable], - snapshot_fn=do_snapshot, - snapshot_block=far_block, - ): - def _actions_snaps(): - _sleep_till_block(snapshot_block, far_ts) - - yield Frame(snap=snapshot_fn(), func="init") - - for action_fn in actions_list: - action_fn() - yield Frame( - snap=snapshot_fn(), - func=repr(action_fn), - ) - - with _chain_snapshot(): - before = tuple(_actions_snaps()) - - execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env) - - # do not call _chain_snapshot here to be able to interact with the environment in the test - after = tuple(_actions_snaps()) - - return before, after - - return _do - - -@pytest.fixture(scope="module") -def far_block() -> int: - return chain.height + 1_000 - - -@pytest.fixture(scope="module") -def far_ts() -> int: - return chain.time() + 14 * 24 * 60 * 60 # 14 days - - -def _sleep_till_block(block: int, ts: int) -> None: - curr_block = web3.eth.get_block_number() - - if curr_block > block: - raise ValueError(f"Current block {curr_block} is greater than the target block {block}") - - print(f"Forwarding chain to block {block}, may take a while...") - chain.mine(block - curr_block, timestamp=ts) - - -def _stacks_equal(stacks: tuple[Stack, Stack]) -> None: - """Compare two stacks, asserting that they are equal""" - - for v1_frame, v2_frame in zip(*stacks, strict=True): - with check: # soft asserts - assert v1_frame["snap"] == v2_frame["snap"], f"Snapshots after {v1_frame['func']} are not equal" diff --git a/tests/snapshot/test_lido_snapshot.py b/tests/snapshot/test_lido_snapshot.py index abfd36996..9461342ce 100644 --- a/tests/snapshot/test_lido_snapshot.py +++ b/tests/snapshot/test_lido_snapshot.py @@ -10,7 +10,7 @@ from web3.types import Wei from tests.conftest import Helpers -from utils.config import contracts, LDO_TOKEN, VOTING, AGENT +from utils.config import contracts, LDO_TOKEN, VOTING, AGENT, INITIAL_MAX_EXTERNAL_RATIO_BP from utils.evm_script import EMPTY_CALLSCRIPT from utils.test.governance_helpers import execute_vote_and_process_dg_proposals from utils.test.snapshot_helpers import _chain_snapshot @@ -32,12 +32,24 @@ class Frame(TypedDict): UINT256_MAX = 2**256 - 1 _1ETH = Wei(10**18) +ZERO_BYTES32 = b'\x00' * 32 + EXPECTED_SNAPSHOT_DIFFS: dict[str, Any] = { - "canPerform()": (True, False), - "getRecoveryVault": (AGENT, ZERO_ADDRESS) + "lido.Lido.beaconBalance": ZERO_BYTES32, + "lido.Lido.beaconValidators": ZERO_BYTES32, + "lido.Lido.bufferedEther": ZERO_BYTES32, + "lido.Lido.depositedValidators": ZERO_BYTES32, + "lido.Lido.lidoLocator": ZERO_BYTES32, + "lido.StETH.totalShares": ZERO_BYTES32, } + +IGNORED_SNAPSHOT_KEYS: set[str] = { + "getFeeDistribution", +} + + def test_lido_no_changes_in_views(sandwich_upgrade: SandwichFn): """Test that no views change during the upgrade process.""" @@ -158,7 +170,8 @@ def test_lido_send_ether_snapshot( assert lido.balanceOf(eth_whale) == 0 assert eth_whale.balance() >= _1ETH - assert el_vault.balance() >= _1ETH + if el_vault.balance() < _1ETH: + eth_whale.transfer(el_vault.address, _1ETH) def get_actions(from_address: Account | None = None): return ( @@ -219,20 +232,21 @@ def get_actions(from_address: Account | None = None): _stacks_equal(stacks) -def test_lido_dao_ops_snapshot(sandwich_upgrade: SandwichFn): +def test_lido_dao_ops_snapshot(sandwich_upgrade: SandwichFn, eth_whale: Account): el_vault = contracts.execution_layer_rewards_vault lido = contracts.lido assert lido.getCurrentStakeLimit() > 0 assert lido.isStakingPaused() is False - assert el_vault.balance() >= _1ETH + if el_vault.balance() < _1ETH: + eth_whale.transfer(el_vault.address, _1ETH) assert lido.isStopped() is False def get_actions(from_address: Account | None = None): return ( _call(lido.pauseStaking, {"from": from_address}), _call(lido.stop, {"from": from_address}), - _call(lido.resumeStaking, {"from": from_address}), + _call(lido.resume, {"from": from_address}), _call(lido.pauseStaking, {"from": from_address}), _call(lido.removeStakingLimit, {"from": from_address}), _call(lido.resumeStaking, {"from": from_address}), @@ -245,7 +259,6 @@ def get_actions(from_address: Account | None = None): ), _call(lido.pauseStaking, {"from": from_address}), _call(lido.setStakingLimit, 17, 3, {"from": from_address}), - _call(lido.resume, {"from": from_address}), _call(lido.stop, {"from": from_address}), ) @@ -294,7 +307,6 @@ def _snap(): "getCurrentStakeLimit": lido.getCurrentStakeLimit(), "getFeeDistribution": lido.getFeeDistribution(), "getFee": lido.getFee(), - "getOracle": lido.getOracle(), "getStakeLimitFullInfo": lido.getStakeLimitFullInfo(), "getTotalELRewardsCollected": lido.getTotalELRewardsCollected(), "getTotalShares": lido.getTotalShares(), @@ -327,8 +339,11 @@ def _snap(): # Lido.sol "lido.Lido.beaconBalance", "lido.Lido.beaconValidators", + "lido.Lido.clBalanceAndClValidators", "lido.Lido.bufferedEther", + "lido.Lido.bufferedEtherAndDepositedValidators", "lido.Lido.depositContract", + "lido.Lido.lidoLocator", "lido.Lido.depositedValidators", "lido.Lido.ELRewardsWithdrawalLimit", "lido.Lido.executionLayerRewardsVault", @@ -343,8 +358,10 @@ def _snap(): "lido.Lido.treasury", "lido.Lido.treasuryFee", "lido.Lido.withdrawalCredentials", + "lido.Lido.lidoLocatorAndMaxExternalRatio", # StETH.sol "lido.StETH.totalShares", + "lido.StETH.totalAndExternalShares", # Pausable.sol "lido.Pausable.activeFlag", # AragonApp.sol @@ -468,6 +485,52 @@ def _stacks_equal(stacks: tuple[Stack, Stack]) -> None: with check: unexpected: dict[str, tuple[Any, Any]] = {} for key, before_val in v1_frame["snap"].items(): + if key == "lido.Lido.bufferedEtherAndDepositedValidators": + if not _combined_buffered_and_deposited_slot_ok( + v1_frame["snap"], + v2_frame["snap"], + ): + unexpected[key] = ( + v1_frame["snap"]["lido.Lido.bufferedEtherAndDepositedValidators"], + v2_frame["snap"]["lido.Lido.bufferedEtherAndDepositedValidators"], + ) + continue + + if key == "lido.Lido.clBalanceAndClValidators": + if not _combined_cl_balance_and_validators_slot_ok( + v1_frame["snap"], + v2_frame["snap"], + ): + unexpected[key] = ( + v1_frame["snap"]["lido.Lido.clBalanceAndClValidators"], + v2_frame["snap"]["lido.Lido.clBalanceAndClValidators"], + ) + continue + + if key == "lido.StETH.totalAndExternalShares": + if not _combined_steth_total_and_external_shares_slot_ok( + v1_frame["snap"], + v2_frame["snap"], + ): + unexpected[key] = ( + v1_frame["snap"]["lido.StETH.totalAndExternalShares"], + v2_frame["snap"]["lido.StETH.totalAndExternalShares"], + ) + continue + + if key == "lido.Lido.lidoLocatorAndMaxExternalRatio": + if not _combined_lido_locator_and_max_external_ratio_slot_ok( + v1_frame["snap"], + v2_frame["snap"], + ): + unexpected[key] = ( + v1_frame["snap"]["lido.Lido.lidoLocatorAndMaxExternalRatio"], + v2_frame["snap"]["lido.Lido.lidoLocatorAndMaxExternalRatio"], + ) + continue + + if key in IGNORED_SNAPSHOT_KEYS: + continue after_val = v2_frame["snap"].get(key) if before_val == after_val: continue @@ -478,3 +541,135 @@ def _stacks_equal(stacks: tuple[Stack, Stack]) -> None: assert not unexpected, ( f"Snapshots after {v1_frame['func']} differ unexpectedly: {unexpected}" ) + + +def _combined_buffered_and_deposited_slot_ok( + v1_snap: dict[str, Any], + v2_snap: dict[str, Any], +) -> bool: + """ + Since v3, buffered ether and deposited validators are packed into one slot: + |------ 128 bit -------|------ 128 bit -------| + | deposited validators | buffered ether | + """ + before_combined = v1_snap["lido.Lido.bufferedEtherAndDepositedValidators"] + after_combined = v2_snap["lido.Lido.bufferedEtherAndDepositedValidators"] + + # Before upgrade the new slot must be empty + if before_combined != ZERO_BYTES32: + return False + + # After upgrade the new slot packs high 128 bits as deposited validators + # and low 128 bits as buffered ether from the old slots. + old_buffered = int.from_bytes( + v1_snap["lido.Lido.bufferedEther"], + byteorder="big", + ) + old_deposited = int.from_bytes( + v1_snap["lido.Lido.depositedValidators"], + byteorder="big", + ) + combined_after = int.from_bytes(after_combined, byteorder="big") + low_mask = (1 << 128) - 1 + buffered_after = combined_after & low_mask + deposited_after = combined_after >> 128 + + return buffered_after == old_buffered and deposited_after == old_deposited + + +def _combined_cl_balance_and_validators_slot_ok( + v1_snap: dict[str, Any], + v2_snap: dict[str, Any], +) -> bool: + """ + Since v3, CL balance and CL validators are packed into one slot: + |----- 128 bit -----|------ 128 bit -------| + | CL validators | CL balance | + """ + before_combined = v1_snap["lido.Lido.clBalanceAndClValidators"] + after_combined = v2_snap["lido.Lido.clBalanceAndClValidators"] + + # Before upgrade the new slot must be empty + if before_combined != ZERO_BYTES32: + return False + + # After upgrade the new slot packs high 128 bits as validators count + # and low 128 bits as CL balance from the old slots. + old_balance = int.from_bytes( + v1_snap["lido.Lido.beaconBalance"], + byteorder="big", + ) + old_validators = int.from_bytes( + v1_snap["lido.Lido.beaconValidators"], + byteorder="big", + ) + combined_after = int.from_bytes(after_combined, byteorder="big") + low_mask = (1 << 128) - 1 + balance_after = combined_after & low_mask + validators_after = combined_after >> 128 + + return balance_after == old_balance and validators_after == old_validators + + +def _combined_steth_total_and_external_shares_slot_ok( + v1_snap: dict[str, Any], + v2_snap: dict[str, Any], +) -> bool: + """ + Since v3, total shares and external shares are packed into one slot: + |------ 128 bit -------|------ 128 bit -------| + | external shares | total shares | + """ + before_combined = v1_snap["lido.StETH.totalAndExternalShares"] + after_combined = v2_snap["lido.StETH.totalAndExternalShares"] + + # Before upgrade the new slot must be empty + if before_combined != ZERO_BYTES32: + return False + + combined_after = int.from_bytes(after_combined, byteorder="big") + low_mask = (1 << 128) - 1 + total_after_slot = combined_after & low_mask + external_after = combined_after >> 128 + + # External shares must be zero after upgrade + if external_after != 0: + return False + + # Total shares (low 128 bits) must equal the old storage slot value + total_shares_before_slot = int.from_bytes( + v1_snap["lido.StETH.totalShares"], + byteorder="big", + ) + return total_after_slot == total_shares_before_slot + + +def _combined_lido_locator_and_max_external_ratio_slot_ok( + v1_snap: dict[str, Any], + v2_snap: dict[str, Any], +) -> bool: + """ + Since v3, max external ratio and lido locator are packed into one slot: + |----- 96 bit -----|------ 160 bit -------| + |max external ratio| lido locator address | + """ + before_combined = v1_snap["lido.Lido.lidoLocatorAndMaxExternalRatio"] + after_combined = v2_snap["lido.Lido.lidoLocatorAndMaxExternalRatio"] + + # Before upgrade the new slot must be empty + if before_combined != ZERO_BYTES32: + return False + + combined_after = int.from_bytes(after_combined, byteorder="big") + + # Low 160 bits is locator address, high 96 bits is max external ratio BP + locator_mask = (1 << 160) - 1 + locator_after = combined_after & locator_mask + max_external_ratio_after = combined_after >> 160 + + if max_external_ratio_after != INITIAL_MAX_EXTERNAL_RATIO_BP: + return False + + # Locator address in the combined slot must match the old locator slot value + before_locator_int = int.from_bytes(v1_snap["lido.Lido.lidoLocator"], byteorder="big") + return locator_after == before_locator_int diff --git a/tests/snapshot/test_plain_submit.py b/tests/snapshot/test_plain_submit.py index 3bcb85684..b53ef7395 100644 --- a/tests/snapshot/test_plain_submit.py +++ b/tests/snapshot/test_plain_submit.py @@ -45,7 +45,6 @@ def snapshot() -> Dict[str, any]: "allowRecoverability(LDO)": lido.allowRecoverability(LDO_TOKEN), "allowRecoverability(StETH)": lido.allowRecoverability(LIDO), "appId": lido.appId(), - "getOracle()": lido.getOracle(), "getInitializationBlock()": lido.getInitializationBlock(), "symbol": lido.symbol(), "getEVMScriptRegistry": lido.getEVMScriptRegistry(), @@ -80,21 +79,19 @@ def steps() -> Dict[str, Dict[str, any]]: before: Dict[str, Dict[str, any]] = steps() chain.revert() - + execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env) after: Dict[str, Dict[str, any]] = steps() step_diffs: Dict[str, Dict[str, ValueChanged]] = {} expected_diffs = { - "canPerform()": ValueChanged(from_val=True, to_val=False), - "getRecoveryVault()": ValueChanged(from_val=contracts.agent.address, to_val=ZERO_ADDRESS), } for step, pair_of_snapshots in dict_zip(before, after).items(): (before, after) = pair_of_snapshots step_diffs[step] = dict_diff(before, after) - + for key in expected_diffs: if key in step_diffs[step] and step_diffs[step][key] == expected_diffs[key]: del step_diffs[step][key] diff --git a/tests/snapshot/test_voting.py b/tests/snapshot/test_voting.py index 905bfeaba..d6ea0d7ba 100644 --- a/tests/snapshot/test_voting.py +++ b/tests/snapshot/test_voting.py @@ -110,7 +110,7 @@ def test_create_wait_enact(helpers, vote_time, call_target, vote_ids_from_env, d for step_name, diff in step_diffs.items(): if not vote_ids_from_env: assert_expected_diffs( - step_name, diff, {"votesLength": ValueChanged(from_val=votesLength + 1, to_val=votesLength + 2)} + step_name, diff, {"votesLength": ValueChanged(from_val=votesLength + 1, to_val=votesLength + 3)} ) assert_no_diffs(step_name, diff) diff --git a/tests/test_2025_12_15.py b/tests/test_2025_12_15.py new file mode 100644 index 000000000..f74348c02 --- /dev/null +++ b/tests/test_2025_12_15.py @@ -0,0 +1,92 @@ +import pytest +import brownie +import tests.utils_test_2025_12_15_lidov3 as lidov3 +import tests.utils_test_2025_12_15_operations as ops + +@pytest.fixture(autouse=True) +def isolation(): + brownie.chain.reset() + +def test_vote_v1_v2_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): + + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, + ) + + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, + ) + + lidov3.enact_and_test_dg(stranger, 6) + + ops.enact_and_test_dg(stranger, 7) + +def test_vote_v1_v2_dg2_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): + + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, + ) + + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, + ) + + ops.enact_and_test_dg(stranger, 7) + + lidov3.enact_and_test_dg(stranger, 6) + +def test_vote_v1_dg1_v2_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): + + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, + ) + + lidov3.enact_and_test_dg(stranger, 6) + + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, + ) + + ops.enact_and_test_dg(stranger, 7) + +def test_vote_v2_v1_dg1_dg2(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): + + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, + ) + + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, + ) + + lidov3.enact_and_test_dg(stranger, 7) + + ops.enact_and_test_dg(stranger, 6) + +def test_vote_v2_v1_dg2_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): + + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, + ) + + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, + ) + + ops.enact_and_test_dg(stranger, 6) + + lidov3.enact_and_test_dg(stranger, 7) + +def test_vote_v2_dg2_v1_dg1(helpers, accounts, ldo_holder, vote_ids_from_env, stranger): + + ops.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 194, 6, + ) + + ops.enact_and_test_dg(stranger, 6) + + lidov3.enact_and_test_voting(helpers, accounts, ldo_holder, vote_ids_from_env, stranger, + 195, 7, + ) + + lidov3.enact_and_test_dg(stranger, 7) diff --git a/tests/utils_test_2025_12_15_lidov3.py b/tests/utils_test_2025_12_15_lidov3.py new file mode 100644 index 000000000..09e3b97c0 --- /dev/null +++ b/tests/utils_test_2025_12_15_lidov3.py @@ -0,0 +1,1529 @@ +from brownie import chain, interface, web3, convert, accounts, reverts, ZERO_ADDRESS +from brownie.network.transaction import TransactionReceipt + +from utils.test.tx_tracing_helpers import ( + group_voting_events_from_receipt, + group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events +) +from utils.evm_script import encode_call_script, encode_error +from utils.voting import find_metadata_by_vote_id +from utils.ipfs import get_lido_vote_cid_from_str +from utils.dual_governance import PROPOSAL_STATUS, wait_for_target_time_to_satisfy_time_constrains +from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event + +from utils.agent import agent_forward +from utils.permissions import encode_oz_grant_role, encode_oz_revoke_role +from utils.test.event_validators.easy_track import validate_evmscript_factory_added_event, EVMScriptFactoryAdded +from utils.easy_track import create_permissions +from utils.test.event_validators.common import validate_events_chain +from utils.test.event_validators.proxy import validate_proxy_upgrade_event +from utils.test.event_validators.permission import validate_grant_role_event, validate_revoke_role_event +from utils.test.event_validators.aragon import validate_aragon_set_app_event, validate_aragon_grant_permission_event, validate_aragon_revoke_permission_event +from utils.test.easy_track_helpers import _encode_calldata, create_and_enact_motion +from utils.test.event_validators.unpause import validate_pause_for_event +from utils.test.event_validators.time_constraints import validate_dg_time_constraints_executed_within_day_time_event + + +# ============================================================================ +# ============================== Import vote ================================= +# ============================================================================ +from scripts.upgrade_2025_12_15_mainnet_v3 import start_vote, get_vote_items + + +# ============================================================================ +# ============================== Constants =================================== +# ============================================================================ + +DEFAULT_ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000" + +# Voting addresses +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" +EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" +DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" +DUAL_GOVERNANCE_TIME_CONSTRAINTS = "0x2a30F5aC03187674553024296bed35Aa49749DDa" +ARAGON_KERNEL = "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc" +ACL = "0x9895F0F17cc1d1891b6f18ee0b483B6f221b37Bb" +EASYTRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" +EASYTRACK_EVMSCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" + +# Old Lido addresses +LIDO_LOCATOR = "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" +ACCOUNTING_ORACLE = "0x852deD011285fe67063a08005c71a85690503Cee" +HASH_CONSENSUS = "0xD624B08C83bAECF0807Dd2c6880C3154a5F0B288" # HashConsensus for AccountingOracle +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +ORACLE_DAEMON_CONFIG = "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09" +CSM_ACCOUNTING = "0x4d72BFF1BeaC69925F8Bd12526a39BAAb069e5Da" +OLD_BURNER = "0xD15a672319Cf0352560eE76d9e89eAB0889046D3" +LIDO_APP_ID = "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320" +WITHDRAWAL_QUEUE = "0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1" + +# Our custom Aragon apps +LIDO = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" +NODE_OPERATORS_REGISTRY = "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" +SIMPLE_DVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" + +# New Lido V3 addresses +VAULT_HUB = "0x1d201BE093d847f6446530Efb0E8Fb426d176709" +ACCOUNTING = "0x23ED611be0e1a820978875C0122F92260804cdDf" +ACCOUNTING_IMPL = "0xd43a3E984071F40d5d840f60708Af0e9526785df" +OPERATOR_GRID = "0xC69685E89Cefc327b43B7234AC646451B27c544d" +LAZY_ORACLE = "0x5DB427080200c235F2Ae8Cd17A7be87921f7AD6c" +PREDEPOSIT_GUARANTEE = "0xF4bF42c6D6A0E38825785048124DBAD6c9eaaac3" +VAULTS_ADAPTER = "0xe2DE6d2DefF15588a71849c0429101F8ca9FB14D" +GATE_SEAL_V3 = "0x881dAd714679A6FeaA636446A0499101375A365c" +LIDO_IMPL = "0x6ca84080381E43938476814be61B779A8bB6a600" +UPGRADE_TEMPLATE = "0x34E01ecFebd403370b0879C628f8A5319dDb8507" +LIDO_LOCATOR_IMPL = "0x2f8779042EFaEd4c53db2Ce293eB6B3f7096C72d" +ACCOUNTING_ORACLE_IMPL = "0x1455B96780A93e08abFE41243Db92E2fCbb0141c" +RESEAL_MANAGER = "0x7914b5a1539b97Bd0bbd155757F25FD79A522d24" +BURNER = "0xE76c52750019b80B43E36DF30bf4060EB73F573a" +VAULTS_FACTORY = "0x02Ca7772FF14a9F6c1a08aF385aA96bb1b34175A" + +# New Easy Track factories +ST_VAULTS_COMMITTEE = "0x18A1065c81b0Cc356F1b1C843ddd5E14e4AefffF" +ALTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0xa29173C7BCf39dA48D5E404146A652d7464aee14" +REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY = "0x194A46DA1947E98c9D79af13E06Cfbee0D8610cC" +REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY = "0x5292A1284e4695B95C0840CF8ea25A818751C17F" +SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY = "0x93F1DEE4473Ee9F42c8257C201e33a6Da30E5d67" +SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY = "0x1dF50522A1D868C12bF71747Bb6F24A18Fe6d32C" +FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY = "0x6C968cD89CA358fbAf57B18e77a8973Fa869a6aA" +UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY = "0x8Bdc726a3147D8187820391D7c6F9F942606aEe6" +UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY = "0x5C3bDFa3E7f312d8cf72F56F2b797b026f6B471c" + +# New versions of apps after upgrade +NEW_LIDO_VERSION = 3 +NEW_ACCOUNTING_ORACLE_VERSION = 4 +NEW_HASH_CONSENSUS_VERSION = 5 + +UTC14 = 60 * 60 * 14 +UTC23 = 60 * 60 * 23 +SLASHING_RESERVE_SHIFT = 8192 +MAX_EXTERNAL_RATIO_BP = 300 # 3% +INFINITE_ALLOWANCE = 2**256 - 1 # type(uint256).max +PAUSE_INFINITELY = 2**256 - 1 # type(uint256).max + + +# ============================================================================ +# ============================== Helper functions ============================ +# ============================================================================ + +def get_ossifiable_proxy_impl(proxy_address): + """Get implementation address from an OssifiableProxy""" + proxy = interface.OssifiableProxy(proxy_address) + return proxy.proxy__getImplementation() + + +# ============================================================================ +# =================== Aragon event validators for DG ========================= +# ============================================================================ + +def validate_config_value_set_event( + event, + key: str, + value: int, + emitted_by: str, +) -> None: + """ + Validate OracleDaemonConfig ConfigValueSet event via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "ConfigValueSet", "ScriptResult", "Executed"] + + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("ConfigValueSet") == 1, f"Expected 1 ConfigValueSet, got {event.count('ConfigValueSet')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" + + assert key == event["ConfigValueSet"][0]["key"], f"Wrong key: expected {key} to be equal to {event['ConfigValueSet'][0]['key']}" + assert convert.to_int(event["ConfigValueSet"][0]["value"]) == value, f"Wrong value: expected {value}, got {convert.to_int(event['ConfigValueSet'][0]['value'])}" + + assert convert.to_address(event["ConfigValueSet"][0]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" + + +def validate_upgrade_started_event(events) -> None: + """ + Validate V3Template UpgradeStarted event via DG proposal. + Ensures only expected events are fired. + """ + _events_chain = ["LogScriptCall", "UpgradeStarted", "ScriptResult", "Executed"] + + validate_events_chain([e.name for e in events], _events_chain) + + assert events.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {events.count('LogScriptCall')}" + assert events.count("UpgradeStarted") == 1, f"Expected 1 UpgradeStarted, got {events.count('UpgradeStarted')}" + assert events.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {events.count('ScriptResult')}" + assert events.count("Executed") == 1, f"Expected 1 Executed, got {events.count('Executed')}" + + assert convert.to_address(events["UpgradeStarted"][0]["_emitted_by"]) == convert.to_address( + UPGRADE_TEMPLATE + ), f"Wrong event emitter: expected {UPGRADE_TEMPLATE}" + + +def validate_upgrade_finished_events(events) -> None: + """ + Validate V3Template UpgradeFinished events via DG proposal. + Ensures only expected events are fired. + """ + _events_chain = ["LogScriptCall", "ContractVersionSet", "Transfer", "TransferShares", "Approval", "Approval", "Approval", "Approval", + "MaxExternalRatioBPSet", "ContractVersionSet", "ConsensusVersionSet", "UpgradeFinished", "ScriptResult", "Executed"] + + validate_events_chain([e.name for e in events], _events_chain) + + assert events.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {events.count('LogScriptCall')}" + assert events.count("ContractVersionSet") == 2, f"Expected 1 ContractVersionSet, got {events.count('ContractVersionSet')}" + assert events.count("Approval") == 4, f"Expected 4 Approval, got {events.count('Approval')}" + assert events.count("MaxExternalRatioBPSet") == 1, f"Expected 1 MaxExternalRatioBPSet, got {events.count('MaxExternalRatioBPSet')}" + assert events.count("ConsensusVersionSet") == 1, f"Expected 1 ConsensusVersionSet, got {events.count('ConsensusVersionSet')}" + assert events.count("UpgradeFinished") == 1, f"Expected 1 UpgradeFinished, got {events.count('UpgradeFinished')}" + assert events.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {events.count('ScriptResult')}" + assert events.count("Executed") == 1, f"Expected 1 Executed, got {events.count('Executed')}" + + lido_version = events["ContractVersionSet"][0]["version"] + assert lido_version == NEW_LIDO_VERSION, f"Wrong version: expected {NEW_LIDO_VERSION}, got {lido_version}" + assert convert.to_address(events["ContractVersionSet"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + # Transfer and TransferShares events are emitted only if old Burner has some shares on balance + if events.count("Transfer") > 0: + assert events.count("Transfer") == 1, "Transfer event should be emitted only once" + assert events.count("TransferShares") == 1, "TransferShares event should be emitted only once" + assert convert.to_address(events["Transfer"][0]["from"]) == OLD_BURNER, f"Wrong from: expected {OLD_BURNER}" + assert convert.to_address(events["Transfer"][0]["to"]) == BURNER, f"Wrong to: expected {BURNER}" + assert convert.to_address(events["Transfer"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + assert convert.to_address(events["TransferShares"][0]["from"]) == OLD_BURNER, f"Wrong from: expected {OLD_BURNER}" + assert convert.to_address(events["TransferShares"][0]["to"]) == BURNER, f"Wrong to: expected {BURNER}" + assert convert.to_address(events["TransferShares"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + assert convert.to_address(events["Approval"][0]["owner"]) == WITHDRAWAL_QUEUE, f"Wrong owner: expected {WITHDRAWAL_QUEUE}" + assert convert.to_address(events["Approval"][0]["spender"]) == OLD_BURNER, f"Wrong spender: expected {OLD_BURNER}" + assert convert.to_uint(events["Approval"][0]["value"]) == 0, f"Wrong value: expected {0}" + assert convert.to_address(events["Approval"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + assert convert.to_address(events["Approval"][1]["owner"]) == WITHDRAWAL_QUEUE, f"Wrong owner: expected {WITHDRAWAL_QUEUE}" + assert convert.to_address(events["Approval"][1]["spender"]) == BURNER, f"Wrong spender: expected {BURNER}" + assert convert.to_uint(events["Approval"][1]["value"]) == INFINITE_ALLOWANCE, f"Wrong value: expected {INFINITE_ALLOWANCE}" + assert convert.to_address(events["Approval"][1]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + assert convert.to_address(events["Approval"][2]["owner"]) == CSM_ACCOUNTING, f"Wrong owner: expected {CSM_ACCOUNTING}" + assert convert.to_address(events["Approval"][2]["spender"]) == OLD_BURNER, f"Wrong spender: expected {OLD_BURNER}" + assert convert.to_uint(events["Approval"][2]["value"]) == 0, f"Wrong value: expected {0}" + assert convert.to_address(events["Approval"][2]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + assert convert.to_address(events["Approval"][3]["owner"]) == CSM_ACCOUNTING, f"Wrong owner: expected {CSM_ACCOUNTING}" + assert convert.to_address(events["Approval"][3]["spender"]) == BURNER, f"Wrong spender: expected {BURNER}" + assert convert.to_uint(events["Approval"][3]["value"]) == INFINITE_ALLOWANCE, f"Wrong value: expected {INFINITE_ALLOWANCE}" + assert convert.to_address(events["Approval"][3]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + max_external_ratio_bp = events["MaxExternalRatioBPSet"][0]["maxExternalRatioBP"] + assert max_external_ratio_bp == MAX_EXTERNAL_RATIO_BP, f"Wrong max external ratio: expected {MAX_EXTERNAL_RATIO_BP}, got {max_external_ratio_bp}" + assert convert.to_address(events["MaxExternalRatioBPSet"][0]["_emitted_by"]) == LIDO, f"Wrong event emitter: expected {LIDO}" + + oracle_version = events["ContractVersionSet"][1]["version"] + assert oracle_version == NEW_ACCOUNTING_ORACLE_VERSION, f"Wrong version: expected {NEW_ACCOUNTING_ORACLE_VERSION}, got {oracle_version}" + assert convert.to_address(events["ContractVersionSet"][1]["_emitted_by"]) == ACCOUNTING_ORACLE, f"Wrong event emitter: expected {ACCOUNTING_ORACLE}" + + consensus_version = events["ConsensusVersionSet"][0]["version"] + assert consensus_version == NEW_HASH_CONSENSUS_VERSION, f"Wrong version: expected {NEW_HASH_CONSENSUS_VERSION}, got {consensus_version}" + assert convert.to_address(events["ConsensusVersionSet"][0]["_emitted_by"]) == ACCOUNTING_ORACLE, f"Wrong event emitter: expected {ACCOUNTING_ORACLE}" + + assert convert.to_address(events["UpgradeFinished"][0]["_emitted_by"]) == convert.to_address( + UPGRADE_TEMPLATE + ), f"Wrong event emitter: expected {UPGRADE_TEMPLATE}" + + +# ============================================================================ +# ============================== Test functions ============================== +# ============================================================================ + +def dual_governance_proposal_calls(): + """Returns list of dual governance proposal calls for events checking""" + + # Helper function to encode proxy upgrades + def encode_proxy_upgrade_to(proxy_contract, new_impl_address): + return (proxy_contract.address, proxy_contract.proxy__upgradeTo.encode_input(new_impl_address)) + + lido_locator_proxy = interface.OssifiableProxy(LIDO_LOCATOR) + upgradeTemplate = interface.UpgradeTemplateV3(UPGRADE_TEMPLATE) + old_burner = interface.Burner(OLD_BURNER) + oracle_daemon_config = interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) + accounting_oracle_proxy = interface.OssifiableProxy(ACCOUNTING_ORACLE) + kernel = interface.Kernel(ARAGON_KERNEL) + acl = interface.ACL(ACL) + staking_router = interface.StakingRouter(STAKING_ROUTER) + predeposit_guarantee = interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) + + dg_items = [ + # 1.1. Ensure DG proposal execution is within daily time window (14:00 UTC - 23:00 UTC) + ( + DUAL_GOVERNANCE_TIME_CONSTRAINTS, + interface.TimeConstraints(DUAL_GOVERNANCE_TIME_CONSTRAINTS).checkTimeWithinDayTimeAndEmit.encode_input( + UTC14, # 14:00 UTC + UTC23 # 23:00 UTC + ), + ), + + # 1.2. Call V3Template.startUpgrade + agent_forward([ + (upgradeTemplate.address, upgradeTemplate.startUpgrade.encode_input()) + ]), + + # 1.3. Upgrade LidoLocator implementation + agent_forward([encode_proxy_upgrade_to(lido_locator_proxy, LIDO_LOCATOR_IMPL)]), + + # 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT + agent_forward([ + (acl.address, + acl.grantPermission.encode_input( + AGENT, + ARAGON_KERNEL, + web3.keccak(text="APP_MANAGER_ROLE") + )) + ]), + + # 1.5. Set Lido implementation in Kernel + agent_forward([ + (kernel.address, + kernel.setApp.encode_input( + kernel.APP_BASES_NAMESPACE(), + LIDO_APP_ID, + LIDO_IMPL + )) + ]), + + # 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT + agent_forward([ + (acl.address, + acl.revokePermission.encode_input( + AGENT, + ARAGON_KERNEL, + web3.keccak(text="APP_MANAGER_ROLE") + )) + ]), + + # 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + agent_forward([ + encode_oz_revoke_role( + contract=old_burner, + role_name="REQUEST_BURN_SHARES_ROLE", + revoke_from=LIDO + ) + ]), + + # 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + agent_forward([ + encode_oz_revoke_role( + contract=old_burner, + role_name="REQUEST_BURN_SHARES_ROLE", + revoke_from=NODE_OPERATORS_REGISTRY + ) + ]), + + # 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + agent_forward([ + encode_oz_revoke_role( + contract=old_burner, + role_name="REQUEST_BURN_SHARES_ROLE", + revoke_from=SIMPLE_DVT + ) + ]), + + # 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + agent_forward([ + encode_oz_revoke_role( + contract=old_burner, + role_name="REQUEST_BURN_SHARES_ROLE", + revoke_from=CSM_ACCOUNTING + ) + ]), + + # 1.11. Upgrade AccountingOracle implementation + agent_forward([encode_proxy_upgrade_to(accounting_oracle_proxy, ACCOUNTING_ORACLE_IMPL)]), + + # 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido + agent_forward([ + encode_oz_revoke_role( + contract=staking_router, + role_name="REPORT_REWARDS_MINTED_ROLE", + revoke_from=LIDO + ) + ]), + + # 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting + agent_forward([ + encode_oz_grant_role( + contract=staking_router, + role_name="REPORT_REWARDS_MINTED_ROLE", + grant_to=ACCOUNTING + ) + ]), + + # 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent + agent_forward([ + encode_oz_grant_role( + contract=oracle_daemon_config, + role_name="CONFIG_MANAGER_ROLE", + grant_to=AGENT + ) + ]), + + # 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + agent_forward([ + (oracle_daemon_config.address, oracle_daemon_config.set.encode_input( + "SLASHING_RESERVE_WE_RIGHT_SHIFT", + web3.codec.encode(['uint256'], [SLASHING_RESERVE_SHIFT]) + )) + ]), + + # 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + agent_forward([ + (oracle_daemon_config.address, oracle_daemon_config.set.encode_input( + "SLASHING_RESERVE_WE_LEFT_SHIFT", + web3.codec.encode(['uint256'], [SLASHING_RESERVE_SHIFT]) + )) + ]), + + # 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent + agent_forward([ + encode_oz_revoke_role( + contract=oracle_daemon_config, + role_name="CONFIG_MANAGER_ROLE", + revoke_from=AGENT + ) + ]), + + # 1.18. Grant PredepositGuarantee's PAUSE_ROLE to Agent + agent_forward([ + encode_oz_grant_role( + contract=predeposit_guarantee, + role_name="PausableUntilWithRoles.PauseRole", + grant_to=AGENT + ) + ]), + + # 1.19. Pause PredepositGuarantee + agent_forward([ + (predeposit_guarantee.address, predeposit_guarantee.pauseFor.encode_input(PAUSE_INFINITELY)) + ]), + + # 1.20. Revoke PredepositGuarantee's PAUSE_ROLE from Agent + agent_forward([ + encode_oz_revoke_role( + contract=predeposit_guarantee, + role_name="PausableUntilWithRoles.PauseRole", + revoke_from=AGENT + ) + ]), + + # 1.21. Call V3Template.finishUpgrade + agent_forward([ + (upgradeTemplate.address, upgradeTemplate.finishUpgrade.encode_input()) + ]), + ] + + # Convert each dg_item to the expected format + proposal_calls = [] + for dg_item in dg_items: + target, data = dg_item # agent_forward returns (target, data) + proposal_calls.append({ + "target": target, + "value": 0, + "data": data + }) + + return proposal_calls + + +def enact_and_test_voting( + helpers, + accounts, + ldo_holder, + vote_ids_from_env, + stranger, + expected_vote_id, + expected_dg_proposal_id, +): + """ + Submit, enact and test the voting proposal. + Includes all before/after voting checks and event validation. + """ + EXPECTED_VOTE_EVENTS_COUNT = 9 + IPFS_DESCRIPTION_HASH = "bafkreibvblxc6urod5fefbfadebtr5fy26ixeliyzctcn6lnwlfk6xxvmm" + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + voting = interface.Voting(VOTING) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + easy_track = interface.EasyTrack(EASYTRACK) + + vault_hub = interface.VaultHub(VAULT_HUB) + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + + # ========================================================================= + # ======================== Identify or Create vote ======================== + # ========================================================================= + if vote_ids_from_env: + vote_id = vote_ids_from_env[0] + if expected_vote_id is not None: + assert vote_id == expected_vote_id + elif expected_vote_id is not None and voting.votesLength() > expected_vote_id: + vote_id = expected_vote_id + else: + vote_id, _ = start_vote({"from": ldo_holder}, silent=True) + + _, call_script_items = get_vote_items() + onchain_script = voting.getVote(vote_id)["script"] + assert onchain_script == encode_call_script(call_script_items) + + + # ========================================================================= + # ============================= Execute Vote ============================== + # ========================================================================= + is_executed = voting.getVote(vote_id)["executed"] + if not is_executed: + # ======================================================================= + # ========================= Before voting checks ======================== + # ======================================================================= + + # Steps 2-9: Add EasyTrack factories + initial_factories = easy_track.getEVMScriptFactories() + assert ALTER_TIERS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have ALTER_TIERS_IN_OPERATOR_GRID_FACTORY factory before vote" + assert REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY factory before vote" + assert REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY factory before vote" + assert SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY factory before vote" + assert SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY not in initial_factories, "EasyTrack should not have SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY factory before vote" + assert FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY not in initial_factories, "EasyTrack should not have FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY factory before vote" + assert UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY factory before vote" + assert UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY not in initial_factories, "EasyTrack should not have UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY factory before vote" + + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH + + vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) + display_voting_events(vote_tx) + + + # ======================================================================= + # ========================= After voting checks ========================= + # ======================================================================= + + # Check roles that are needed for Easy Track factories + registry_role = web3.keccak(text="vaults.OperatorsGrid.Registry") + assert operator_grid.hasRole(registry_role, VAULTS_ADAPTER), "Operator Grid should have REGISTRY_ROLE on VAULTS_ADAPTER after upgrade" + assert operator_grid.hasRole(registry_role, EASYTRACK_EVMSCRIPT_EXECUTOR), "Operator Grid should have REGISTRY_ROLE on EASYTRACK_EVMSCRIPT_EXECUTOR after upgrade" + validator_exit_role = web3.keccak(text="vaults.VaultHub.ValidatorExitRole") + assert vault_hub.hasRole(validator_exit_role, VAULTS_ADAPTER), "Vault Hub should have VALIDATOR_EXIT_ROLE on VAULTS_ADAPTER after upgrade" + bad_debt_master_role = web3.keccak(text="vaults.VaultHub.BadDebtMasterRole") + assert vault_hub.hasRole(bad_debt_master_role, VAULTS_ADAPTER), "Vault Hub should have BAD_DEBT_MASTER_ROLE on VAULTS_ADAPTER after upgrade" + + # Steps 2-9: Add EasyTrack factories + new_factories = easy_track.getEVMScriptFactories() + assert ALTER_TIERS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have ALTER_TIERS_IN_OPERATOR_GRID_FACTORY factory after vote" + assert REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY factory after vote" + assert REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY factory after vote" + assert SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY factory after vote" + assert SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY in new_factories, "EasyTrack should have SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY factory after vote" + assert FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY in new_factories, "EasyTrack should have FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY factory after vote" + assert UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY factory after vote" + assert UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY in new_factories, "EasyTrack should have UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY factory after vote" + + # Check that after the Aragon vote has passed, creation of the vaults via VaultFactory still reverts + with reverts(): + interface.VaultFactory(VAULTS_FACTORY).createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, + [], + {"from": stranger, "value": "1 ether"}, + ) + + vote_events = group_voting_events_from_receipt(vote_tx) + assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT + assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT + + if expected_dg_proposal_id is not None: + assert expected_dg_proposal_id == timelock.getProposalsCount() + + validate_dual_governance_submit_event( + vote_events[0], + proposal_id=expected_dg_proposal_id, + proposer=VOTING, + executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + metadata="Activate Lido V3: Phase 1 (Soft Launch)", + proposal_calls=dual_governance_proposal_calls(), + ) + + # Validate EasyTrack bypass events for new factories + validate_evmscript_factory_added_event( + event=vote_events[1], + p=EVMScriptFactoryAdded( + factory_addr=ALTER_TIERS_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(operator_grid, "alterTiers") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[2], + p=EVMScriptFactoryAdded( + factory_addr=REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(operator_grid, "registerGroup") + create_permissions(operator_grid, "registerTiers")[2:] + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[3], + p=EVMScriptFactoryAdded( + factory_addr=REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(operator_grid, "registerTiers") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[4], + p=EVMScriptFactoryAdded( + factory_addr=UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(operator_grid, "updateGroupShareLimit") + ), + emitted_by=easy_track, + ) + + vaults_adapter = interface.IVaultsAdapter(VAULTS_ADAPTER) + validate_evmscript_factory_added_event( + event=vote_events[5], + p=EVMScriptFactoryAdded( + factory_addr=SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(vaults_adapter, "setVaultJailStatus") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[6], + p=EVMScriptFactoryAdded( + factory_addr=UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY, + permissions=create_permissions(vaults_adapter, "updateVaultFees") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[7], + p=EVMScriptFactoryAdded( + factory_addr=FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY, + permissions=create_permissions(vaults_adapter, "forceValidatorExit") + ), + emitted_by=easy_track, + ) + + validate_evmscript_factory_added_event( + event=vote_events[8], + p=EVMScriptFactoryAdded( + factory_addr=SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY, + permissions=create_permissions(vaults_adapter, "socializeBadDebt") + ), + emitted_by=easy_track, + ) + + +def enact_and_test_dg(stranger, expected_dg_proposal_id): + """ + Enact and test the dual governance proposal. + Includes all before/after DG checks and event validation. + """ + if expected_dg_proposal_id is None: + return + + EXPECTED_DG_EVENTS_FROM_AGENT = 20 + EXPECTED_DG_EVENTS_COUNT = 21 + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + kernel = interface.Kernel(ARAGON_KERNEL) + acl = interface.ACL(ACL) + + lido_locator_proxy = interface.OssifiableProxy(LIDO_LOCATOR) + accounting_oracle_proxy = interface.OssifiableProxy(ACCOUNTING_ORACLE) + staking_router = interface.StakingRouter(STAKING_ROUTER) + old_burner = interface.Burner(OLD_BURNER) + oracle_daemon_config = interface.OracleDaemonConfig(ORACLE_DAEMON_CONFIG) + upgradeTemplate = interface.UpgradeTemplateV3(UPGRADE_TEMPLATE) + lido = interface.Lido(LIDO) + + vault_hub = interface.VaultHub(VAULT_HUB) + operator_grid = interface.OperatorGrid(OPERATOR_GRID) + lazy_oracle = interface.LazyOracle(LAZY_ORACLE) + vault_factory = interface.VaultFactory(VAULTS_FACTORY) + predeposit_guarantee = interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) + + # Save original implementations for comparison + locator_impl_before = get_ossifiable_proxy_impl(LIDO_LOCATOR) + accounting_oracle_impl_before = get_ossifiable_proxy_impl(ACCOUNTING_ORACLE) + + report_rewards_minted_role = web3.keccak(text="REPORT_REWARDS_MINTED_ROLE") + request_burn_shares_role = web3.keccak(text="REQUEST_BURN_SHARES_ROLE") + config_manager_role = web3.keccak(text="CONFIG_MANAGER_ROLE") + app_manager_role = web3.keccak(text="APP_MANAGER_ROLE") + pdg_pause_role = web3.keccak(text="PausableUntilWithRoles.PauseRole") + + details = timelock.getProposalDetails(expected_dg_proposal_id) + if details["status"] != PROPOSAL_STATUS["executed"]: + # ========================================================================= + # ================== DG before proposal executed checks =================== + # ========================================================================= + + # Step 1.2. Call V3Template.startUpgrade + assert upgradeTemplate.upgradeBlockNumber() == 0, "V3Template should have upgradeBlockNumber 0 before startUpgrade" + assert upgradeTemplate.initialTotalShares() == 0, "V3Template should have initialTotalShares 0 before startUpgrade" + assert upgradeTemplate.initialTotalPooledEther() == 0, "V3Template should have initialTotalPooledEther 0 before startUpgrade" + assert upgradeTemplate.initialOldBurnerStethSharesBalance() == 0, "V3Template should have initialOldBurnerStethSharesBalance 0 before startUpgrade" + initial_total_shares_before = lido.getTotalShares() + initial_total_pooled_ether_before = lido.getTotalPooledEther() + initial_burner_steth_shares_balance_before = lido.sharesOf(OLD_BURNER) + + # Step 1.3: Check Lido Locator implementation initial state + assert locator_impl_before != LIDO_LOCATOR_IMPL, "Locator implementation should be different before upgrade" + + # Step 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT + assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE before upgrade" + + # Step 1.5. Set Lido implementation in Kernel + assert not kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be different before upgrade" + + # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + assert old_burner.hasRole(request_burn_shares_role, LIDO), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Lido before upgrade" + + # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + assert old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Curated staking module before upgrade" + + # Step 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + assert old_burner.hasRole(request_burn_shares_role, SIMPLE_DVT), "Old Burner should have REQUEST_BURN_SHARES_ROLE on SimpleDVT before upgrade" + + # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + assert old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting before upgrade" + + # Step 1.11: Check Accounting Oracle implementation initial state + assert accounting_oracle_impl_before != ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be different before upgrade" + + # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido + assert staking_router.hasRole(report_rewards_minted_role, LIDO), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Lido before upgrade" + + # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting + assert not staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Accounting before upgrade" + + # Step 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent + assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent before upgrade" + + # Step 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + try: + oracle_daemon_config.get('SLASHING_RESERVE_WE_RIGHT_SHIFT') + assert False, "SLASHING_RESERVE_WE_RIGHT_SHIFT should not exist before vote" + except Exception: + pass # Expected to fail + + # Step 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + try: + oracle_daemon_config.get('SLASHING_RESERVE_WE_LEFT_SHIFT') + assert False, "SLASHING_RESERVE_WE_LEFT_SHIFT should not exist before vote" + except Exception: + pass # Expected to fail + + # Step 1.18. Grant PredepositGuarantee's PAUSE_ROLE to Agent + assert not predeposit_guarantee.hasRole(pdg_pause_role, AGENT), "PredepositGuarantee should not have PAUSE_ROLE on Agent before upgrade" + + # Step 1.19. Pause PredepositGuarantee + assert predeposit_guarantee.isPaused() == False, "PredepositGuarantee should not be paused before upgrade" + assert predeposit_guarantee.getResumeSinceTimestamp() == 0, "PredepositGuarantee should have getResumeSinceTimestamp 0 before upgrade" + + # Step 1.21. Call V3Template.finishUpgrade + assert lido.getContractVersion() == NEW_LIDO_VERSION - 1, "LIDO should have version 2 before finishUpgrade" + assert lido.allowance(CSM_ACCOUNTING, BURNER) == 0, "No allowance from CSM_ACCOUNTING to BURNER before finishUpgrade" + assert lido.allowance(CSM_ACCOUNTING, OLD_BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from CSM_ACCOUNTING to OLD_BURNER before finishUpgrade" + assert lido.allowance(WITHDRAWAL_QUEUE, BURNER) == 0, "No allowance from WITHDRAWAL_QUEUE to BURNER before finishUpgrade" + assert lido.allowance(WITHDRAWAL_QUEUE, OLD_BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from WITHDRAWAL_QUEUE to OLD_BURNER before finishUpgrade" + + accounting_oracle = interface.AccountingOracle(ACCOUNTING_ORACLE) + assert accounting_oracle.getContractVersion() == NEW_ACCOUNTING_ORACLE_VERSION - 1, "AccountingOracle should have version 3 before finishUpgrade" + assert accounting_oracle.getConsensusVersion() == NEW_HASH_CONSENSUS_VERSION - 1, "HashConsensus should have version 4 before finishUpgrade" + + assert upgradeTemplate.isUpgradeFinished() == False, "V3Template should have isUpgradeFinished False before finishUpgrade" + + if details["status"] == PROPOSAL_STATUS["submitted"]: + chain.sleep(timelock.getAfterSubmitDelay() + 1) + dual_governance.scheduleProposal(expected_dg_proposal_id, {"from": stranger}) + + if timelock.getProposalDetails(expected_dg_proposal_id)["status"] == PROPOSAL_STATUS["scheduled"]: + chain.sleep(timelock.getAfterScheduleDelay() + 1) + + wait_for_target_time_to_satisfy_time_constrains() + + dg_tx: TransactionReceipt = timelock.execute(expected_dg_proposal_id, {"from": stranger}) + display_dg_events(dg_tx) + dg_events = group_dg_events_from_receipt( + dg_tx, + timelock=EMERGENCY_PROTECTED_TIMELOCK, + admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + ) + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_FROM_AGENT + assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + + # === DG EXECUTION EVENTS VALIDATION === + + # 1.1. Check execution time window (14:00–23:00 UTC) + validate_dg_time_constraints_executed_within_day_time_event( + dg_events[0], + UTC14, + UTC23, + emitted_by=DUAL_GOVERNANCE_TIME_CONSTRAINTS + ) + + # 1.2. Call V3Template.startUpgrade + validate_upgrade_started_event(dg_events[1]) + + # 1.3. Lido Locator upgrade events + validate_proxy_upgrade_event(dg_events[2], LIDO_LOCATOR_IMPL, emitted_by=lido_locator_proxy) + + # 1.4. Grant Aragon APP_MANAGER_ROLE to the AGENT + validate_aragon_grant_permission_event( + dg_events[3], + entity=AGENT, + app=ARAGON_KERNEL, + role=app_manager_role.hex(), + emitted_by=ACL, + ) + + # 1.5. Set Lido implementation in Kernel + validate_aragon_set_app_event( + dg_events[4], + app_id=LIDO_APP_ID, + app=LIDO_IMPL, + emitted_by=ARAGON_KERNEL, + ) + + # 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT + validate_aragon_revoke_permission_event( + dg_events[5], + entity=AGENT, + app=ARAGON_KERNEL, + role=app_manager_role.hex(), + emitted_by=ACL, + ) + + # 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + validate_revoke_role_event( + dg_events[6], + role=request_burn_shares_role.hex(), + revoke_from=LIDO, + sender=AGENT, + emitted_by=old_burner, + ) + + # 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + validate_revoke_role_event( + dg_events[7], + role=request_burn_shares_role.hex(), + revoke_from=NODE_OPERATORS_REGISTRY, + sender=AGENT, + emitted_by=old_burner, + ) + + # 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + validate_revoke_role_event( + dg_events[8], + role=request_burn_shares_role.hex(), + revoke_from=SIMPLE_DVT, + sender=AGENT, + emitted_by=old_burner, + ) + + # 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + validate_revoke_role_event( + dg_events[9], + role=request_burn_shares_role.hex(), + revoke_from=CSM_ACCOUNTING, + sender=AGENT, + emitted_by=old_burner, + ) + + # 1.11. Accounting Oracle upgrade events + validate_proxy_upgrade_event(dg_events[10], ACCOUNTING_ORACLE_IMPL, emitted_by=accounting_oracle_proxy) + + # 1.12. Revoke Staking Router REPORT_REWARDS_MINTED_ROLE from the Lido + validate_revoke_role_event( + dg_events[11], + role=report_rewards_minted_role.hex(), + revoke_from=LIDO, + sender=AGENT, + emitted_by=staking_router, + ) + + # 1.13. Grant Staking Router REPORT_REWARDS_MINTED_ROLE to Accounting + validate_grant_role_event( + dg_events[12], + role=report_rewards_minted_role.hex(), + grant_to=ACCOUNTING, + sender=AGENT, + emitted_by=staking_router, + ) + + # 1.14. Grant OracleDaemonConfig's CONFIG_MANAGER_ROLE to Agent + validate_grant_role_event( + dg_events[13], + role=config_manager_role.hex(), + grant_to=AGENT, + sender=AGENT, + emitted_by=oracle_daemon_config, + ) + + # 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + validate_config_value_set_event( + dg_events[14], + key='SLASHING_RESERVE_WE_RIGHT_SHIFT', + value=SLASHING_RESERVE_SHIFT, + emitted_by=oracle_daemon_config, + ) + + # 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + validate_config_value_set_event( + dg_events[15], + key='SLASHING_RESERVE_WE_LEFT_SHIFT', + value=SLASHING_RESERVE_SHIFT, + emitted_by=oracle_daemon_config, + ) + + # 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent + validate_revoke_role_event( + dg_events[16], + role=config_manager_role.hex(), + revoke_from=AGENT, + sender=AGENT, + emitted_by=oracle_daemon_config, + ) + + # 1.18. Grant PredepositGuarantee's PAUSE_ROLE to Agent + validate_grant_role_event( + dg_events[17], + role=pdg_pause_role.hex(), + grant_to=AGENT, + sender=AGENT, + emitted_by=predeposit_guarantee, + ) + + # 1.19. Pause PredepositGuarantee + validate_pause_for_event( + dg_events[18], + pause_for=PAUSE_INFINITELY, + sender=AGENT, + emitted_by=predeposit_guarantee, + ) + + # 1.20. Revoke PredepositGuarantee's PAUSE_ROLE from Agent + validate_revoke_role_event( + dg_events[19], + role=pdg_pause_role.hex(), + revoke_from=AGENT, + sender=AGENT, + emitted_by=predeposit_guarantee, + ) + + # 1.21. Call V3Template.finishUpgrade + validate_upgrade_finished_events(dg_events[20]) + + # ========================================================================= + # ==================== After DG proposal executed checks ================== + # ========================================================================= + + # Step 1.2. Call V3Template.startUpgrade + assert upgradeTemplate.upgradeBlockNumber() != 0, "V3Template should have upgradeBlockNumber not 0 after startUpgrade" + assert upgradeTemplate.initialTotalShares() == initial_total_shares_before, "V3Template should have initialTotalShares equal to the initial total shares before upgrade" + assert upgradeTemplate.initialTotalPooledEther() == initial_total_pooled_ether_before, "V3Template should have initialTotalPooledEther equal to the initial total pooled ether before upgrade" + assert upgradeTemplate.initialOldBurnerStethSharesBalance() == initial_burner_steth_shares_balance_before, "V3Template should have initialOldBurnerStethSharesBalance equal to the initial burner steth shares balance before upgrade" + + # Step 1.3: Validate Lido Locator implementation was updated + assert get_ossifiable_proxy_impl(lido_locator_proxy) == LIDO_LOCATOR_IMPL, "Locator implementation should be updated to the new value" + + # Step 1.5. Set Lido implementation in Kernel + assert kernel.getApp(kernel.APP_BASES_NAMESPACE(), LIDO_APP_ID) == LIDO_IMPL, "Lido implementation should be updated to the new value" + + # Step 1.6. Revoke Aragon APP_MANAGER_ROLE from the AGENT + assert not acl.hasPermission(AGENT, ARAGON_KERNEL, app_manager_role), "AGENT should not have APP_MANAGER_ROLE after upgrade" + + # Step 1.7. Revoke REQUEST_BURN_SHARES_ROLE from Lido + assert not old_burner.hasRole(request_burn_shares_role, LIDO), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Lido after upgrade" + + # Step 1.8. Revoke REQUEST_BURN_SHARES_ROLE from Curated staking module + assert not old_burner.hasRole(request_burn_shares_role, NODE_OPERATORS_REGISTRY), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Curated staking module after upgrade" + + # Step 1.9. Revoke REQUEST_BURN_SHARES_ROLE from SimpleDVT + assert not old_burner.hasRole(request_burn_shares_role, SIMPLE_DVT), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on SimpleDVT after upgrade" + + # Step 1.10. Revoke REQUEST_BURN_SHARES_ROLE from Community Staking Accounting + assert not old_burner.hasRole(request_burn_shares_role, CSM_ACCOUNTING), "Old Burner should not have REQUEST_BURN_SHARES_ROLE on Community Staking Accounting after upgrade" + + # Step 1.11: Validate Accounting Oracle implementation was updated + assert get_ossifiable_proxy_impl(accounting_oracle_proxy) == ACCOUNTING_ORACLE_IMPL, "Accounting Oracle implementation should be updated to the new value" + + # Step 1.12. Revoke REPORT_REWARDS_MINTED_ROLE from Lido + assert not staking_router.hasRole(report_rewards_minted_role, LIDO), "Staking Router should not have REPORT_REWARDS_MINTED_ROLE on Lido after upgrade" + + # Step 1.13. Grant REPORT_REWARDS_MINTED_ROLE to Accounting + assert staking_router.hasRole(report_rewards_minted_role, ACCOUNTING), "Staking Router should have REPORT_REWARDS_MINTED_ROLE on Accounting after upgrade" + + # Step 1.15. Set SLASHING_RESERVE_WE_RIGHT_SHIFT to 0x2000 at OracleDaemonConfig + assert convert.to_uint(oracle_daemon_config.get("SLASHING_RESERVE_WE_RIGHT_SHIFT")) == SLASHING_RESERVE_SHIFT, "OracleDaemonConfig should have SLASHING_RESERVE_WE_RIGHT_SHIFT set to 0x2000 after upgrade" + + # Step 1.16. Set SLASHING_RESERVE_WE_LEFT_SHIFT to 0x2000 at OracleDaemonConfig + assert convert.to_uint(oracle_daemon_config.get("SLASHING_RESERVE_WE_LEFT_SHIFT")) == SLASHING_RESERVE_SHIFT, "OracleDaemonConfig should have SLASHING_RESERVE_WE_LEFT_SHIFT set to 0x2000 after upgrade" + + # Step 1.17. Revoke OracleDaemonConfig's CONFIG_MANAGER_ROLE from Agent + assert not oracle_daemon_config.hasRole(config_manager_role, AGENT), "OracleDaemonConfig should not have CONFIG_MANAGER_ROLE on Agent after upgrade" + + # Step 1.19. Pause PredepositGuarantee + assert predeposit_guarantee.isPaused() == True, "PredepositGuarantee should be paused after upgrade" + assert predeposit_guarantee.getResumeSinceTimestamp() == PAUSE_INFINITELY, "PredepositGuarantee should have getResumeSinceTimestamp PAUSE_INFINITELY after upgrade" + + # Step 1.20. Revoke PredepositGuarantee's PAUSE_ROLE from Agent + assert not predeposit_guarantee.hasRole(pdg_pause_role, AGENT), "PredepositGuarantee should not have PAUSE_ROLE on Agent after upgrade" + + # Step 1.21. Call V3Template.finishUpgrade + lido = interface.Lido(LIDO) + assert lido.getContractVersion() == NEW_LIDO_VERSION, "LIDO should have version 3 after finishUpgrade" + assert lido.getMaxExternalRatioBP() == MAX_EXTERNAL_RATIO_BP, "LIDO should have max external ratio 3% after finishUpgrade" + assert lido.allowance(CSM_ACCOUNTING, OLD_BURNER) == 0, "No allowance from CSM_ACCOUNTING to OLD_BURNER after finishUpgrade" + assert lido.allowance(CSM_ACCOUNTING, BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from CSM_ACCOUNTING to BURNER after finishUpgrade" + assert lido.allowance(WITHDRAWAL_QUEUE, OLD_BURNER) == 0, "No allowance from WITHDRAWAL_QUEUE to OLD_BURNER after finishUpgrade" + assert lido.allowance(WITHDRAWAL_QUEUE, BURNER) == INFINITE_ALLOWANCE, "Infinite allowance from WITHDRAWAL_QUEUE to BURNER after finishUpgrade" + + accounting_oracle = interface.AccountingOracle(ACCOUNTING_ORACLE) + assert accounting_oracle.getContractVersion() == NEW_ACCOUNTING_ORACLE_VERSION, "AccountingOracle should have version 4 after finishUpgrade" + assert accounting_oracle.getConsensusVersion() == NEW_HASH_CONSENSUS_VERSION, "HashConsensus should have version 5 after finishUpgrade" + assert upgradeTemplate.isUpgradeFinished() == True, "V3Template should have isUpgradeFinished True after finishUpgrade" + + # Check that a second call to finishUpgrade reverts + agent_account = accounts.at(AGENT, force=True) + with reverts(encode_error("UpgradeAlreadyFinished()")): + upgradeTemplate.finishUpgrade({"from": agent_account}) + + # Check that after the DG proposal has passed, creation of the vaults via VaultFactory can be done + creation_tx = vault_factory.createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": "1 ether"}, + ) + assert creation_tx.events.count("VaultCreated") == 1 + assert creation_tx.events.count("DashboardCreated") == 1 + + # Scenario tests for Easy Track factories behavior after the vote + trusted_address = accounts.at(ST_VAULTS_COMMITTEE, force=True) + easy_track = interface.EasyTrack(EASYTRACK) + chain.snapshot() + test_register_groups_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger, operator_grid) + test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger, operator_grid, vault_factory) + test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory) + test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory) + test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, operator_grid, lazy_oracle, vault_hub, vault_factory) + chain.revert() + + +def test_register_groups_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): + + operator_addresses = [ + "0x0000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000002", + ] + share_limits = [1000, 5000] + tiers_params_array = [ + [(500, 200, 100, 50, 40, 10), (800, 200, 100, 50, 40, 10)], + [(800, 200, 100, 50, 40, 10), (800, 200, 100, 50, 40, 10)], + ] + + calldata = _encode_calldata( + ["address[]", "uint256[]", "(uint256,uint256,uint256,uint256,uint256,uint256)[][]"], + [operator_addresses, share_limits, tiers_params_array] + ) + + # Check initial state + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert group[0] == ZERO_ADDRESS # operator + assert group[1] == 0 # shareLimit + assert len(group[3]) == 0 # tiersId array should be empty + + create_and_enact_motion(easy_track, trusted_address, REGISTER_GROUPS_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert group[0] == operator_address # operator + assert group[1] == share_limits[i] # shareLimit + assert len(group[3]) == len(tiers_params_array[i]) # tiersId array should have the same length as tiers_params + + # Check tier details + for j, tier_id in enumerate(group[3]): + tier = operator_grid.tier(tier_id) + assert tier[1] == tiers_params_array[i][j][0] # shareLimit + assert tier[3] == tiers_params_array[i][j][1] # reserveRatioBP + assert tier[4] == tiers_params_array[i][j][2] # forcedRebalanceThresholdBP + assert tier[5] == tiers_params_array[i][j][3] # infraFeeBP + assert tier[6] == tiers_params_array[i][j][4] # liquidityFeeBP + assert tier[7] == tiers_params_array[i][j][5] # reservationFeeBP + + +def test_register_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): + + # Define operator addresses + operator_addresses = [ + "0x0000000000000000000000000000000000000003", + "0x0000000000000000000000000000000000000004" + ] + + # Define tier parameters for each operator + tiers_params_array = [ + [ # Tiers for operator 1 + (500, 200, 100, 50, 40, 10), + (300, 150, 75, 25, 20, 5), + ], + [ # Tiers for operator 2 + (800, 250, 125, 60, 50, 15), + (400, 180, 90, 30, 25, 8), + ] + ] + + # First register the groups to add tiers to + executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) + for operator_address in operator_addresses: + operator_grid.registerGroup(operator_address, 1000, {"from": executor}) + + # Check initial state - no tiers + for operator_address in operator_addresses: + group = operator_grid.group(operator_address) + assert len(group[3]) == 0 # tiersId array should be empty + + calldata = _encode_calldata( + ["address[]", "(uint256,uint256,uint256,uint256,uint256,uint256)[][]"], + [operator_addresses, tiers_params_array] + ) + + create_and_enact_motion(easy_track, trusted_address, REGISTER_TIERS_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state - tiers should be registered + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert len(group[3]) == len(tiers_params_array[i]) # tiersId array should have the same length as tiers_params + + # Check tier details + for j, tier_id in enumerate(group[3]): + tier = operator_grid.tier(tier_id) + assert tier[1] == tiers_params_array[i][j][0] # shareLimit + assert tier[3] == tiers_params_array[i][j][1] # reserveRatioBP + assert tier[4] == tiers_params_array[i][j][2] # forcedRebalanceThresholdBP + assert tier[5] == tiers_params_array[i][j][3] # infraFeeBP + assert tier[6] == tiers_params_array[i][j][4] # liquidityFeeBP + assert tier[7] == tiers_params_array[i][j][5] # reservationFeeBP + + +def test_alter_tiers_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): + + # Define new tier parameters + # (shareLimit, reserveRatioBP, forcedRebalanceThresholdBP, infraFeeBP, liquidityFeeBP, reservationFeeBP) + new_tier_params = [(2000, 300, 150, 75, 60, 20), (3000, 400, 200, 100, 80, 30)] + + # First register a group and tier to alter + executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) + operator_address = "0x0000000000000000000000000000000000000005" + operator_grid.registerGroup(operator_address, 10000, {"from": executor}) + initial_tier_params = [(1000, 200, 100, 50, 40, 10), (1000, 200, 100, 50, 40, 10)] + operator_grid.registerTiers(operator_address, initial_tier_params, {"from": executor}) + + tiers_count = operator_grid.tiersCount() + tier_ids = [tiers_count - 2, tiers_count - 1] + + # Check initial state + for i, tier_id in enumerate(tier_ids): + tier = operator_grid.tier(tier_id) + assert tier[1] == initial_tier_params[i][0] # shareLimit + assert tier[3] == initial_tier_params[i][1] # reserveRatioBP + assert tier[4] == initial_tier_params[i][2] # forcedRebalanceThresholdBP + assert tier[5] == initial_tier_params[i][3] # infraFeeBP + assert tier[6] == initial_tier_params[i][4] # liquidityFeeBP + assert tier[7] == initial_tier_params[i][5] # reservationFeeBP + + calldata = _encode_calldata(["uint256[]", "(uint256,uint256,uint256,uint256,uint256,uint256)[]"], [tier_ids, new_tier_params]) + + create_and_enact_motion(easy_track, trusted_address, ALTER_TIERS_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state + for i, tier_id in enumerate(tier_ids): + tier = operator_grid.tier(tier_id) + assert tier[1] == new_tier_params[i][0] # shareLimit + assert tier[3] == new_tier_params[i][1] # reserveRatioBP + assert tier[4] == new_tier_params[i][2] # forcedRebalanceThresholdBP + assert tier[5] == new_tier_params[i][3] # infraFeeBP + assert tier[6] == new_tier_params[i][4] # liquidityFeeBP + assert tier[7] == new_tier_params[i][5] # reservationFeeBP + + +def test_update_groups_share_limit_in_operator_grid(easy_track, trusted_address, stranger, operator_grid): + + operator_addresses = ["0x0000000000000000000000000000000000000006", "0x0000000000000000000000000000000000000007"] + new_share_limits = [2000, 3000] + + # First register the group to update + executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) + for i, operator_address in enumerate(operator_addresses): + operator_grid.registerGroup(operator_address, new_share_limits[i]*2, {"from": executor}) + + # Check initial state + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert group[0] == operator_address # operator + assert group[1] == new_share_limits[i]*2 # shareLimit + + calldata = _encode_calldata( + ["address[]", "uint256[]"], + [operator_addresses, new_share_limits] + ) + + create_and_enact_motion(easy_track, trusted_address, UPDATE_GROUPS_SHARE_LIMIT_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state + for i, operator_address in enumerate(operator_addresses): + group = operator_grid.group(operator_address) + assert group[0] == operator_address # operator + assert group[1] == new_share_limits[i] # shareLimit + + +def test_set_jail_status_in_operator_grid(easy_track, trusted_address, stranger, operator_grid, vault_factory): + + # First create the vaults + vaults = [] + for i in range(2): + creation_tx = vault_factory.createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": "1 ether"}, + ) + vaults.append(creation_tx.events["VaultCreated"][0]["vault"]) + + # Check initial state + for vault in vaults: + is_in_jail = operator_grid.isVaultInJail(vault) + assert is_in_jail == False + + calldata = _encode_calldata(["address[]", "bool[]"], [vaults, [True, True]]) + + create_and_enact_motion(easy_track, trusted_address, SET_JAIL_STATUS_IN_OPERATOR_GRID_FACTORY, calldata, stranger) + + # Check final state + for i, vault in enumerate(vaults): + is_in_jail = operator_grid.isVaultInJail(vault) + assert is_in_jail == True + + +def test_update_vaults_fees_in_operator_grid(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory): + + initial_total_value = 2 * 10**18 + + # First create the vault + creation_tx = vault_factory.createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": initial_total_value}, + ) + vault = creation_tx.events["VaultCreated"][0]["vault"] + + # Check initial state + connection = vault_hub.vaultConnection(vault) + assert connection[6] != 1 # infraFeeBP + assert connection[7] != 1 # liquidityFeeBP + assert connection[8] == 0 # reservationFeeBP + + calldata = _encode_calldata(["address[]", "uint256[]", "uint256[]", "uint256[]"], [[vault], [1], [1], [0]]) + + motions_before = easy_track.getMotions() + tx = easy_track.createMotion(UPDATE_VAULTS_FEES_IN_OPERATOR_GRID_FACTORY, calldata, {"from": trusted_address}) + motions = easy_track.getMotions() + assert len(motions) == len(motions_before) + 1 + + ( + motion_id, + _, + _, + motion_duration, + motion_start_date, + _, + _, + _, + _, + ) = motions[-1] + + chain.mine(1, motion_start_date + motion_duration + 1) + + # bring fresh report for vault + current_time = chain.time() + accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) + lazy_oracle.updateReportData( + current_time, + 1000, + "0x00", + "0x00", + {"from": accounting_oracle}) + + lazy_oracle_account = accounts.at(LAZY_ORACLE, force=True) + vault_hub.applyVaultReport( + vault, + current_time, + initial_total_value, + initial_total_value, + 0, + 0, + 0, + 0, + {"from": lazy_oracle_account}) + + easy_track.enactMotion( + motion_id, + tx.events["MotionCreated"]["_evmScriptCallData"], + {"from": stranger}, + ) + + # Check final state + connection = vault_hub.vaultConnection(vault) + assert connection[6] == 1 # infraFeeBP + assert connection[7] == 1 # liquidityFeeBP + assert connection[8] == 0 # reservationFeeBP + + +def test_force_validator_exits_in_vault_hub(easy_track, trusted_address, stranger, lazy_oracle, vault_hub, vault_factory): + + initial_total_value = 2 * 10**18 + + # top up VAULTS_ADAPTER + stranger.transfer(VAULTS_ADAPTER, 10**18) + + pubkey = b"01" * 48 + # First create the vault + creation_tx = vault_factory.createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": initial_total_value}, + ) + vault = creation_tx.events["VaultCreated"][0]["vault"] + + calldata = _encode_calldata(["address[]", "bytes[]"], [[vault], [pubkey]]) + + motions_before = easy_track.getMotions() + tx = easy_track.createMotion(FORCE_VALIDATOR_EXITS_IN_VAULT_HUB_FACTORY, calldata, {"from": trusted_address}) + motions = easy_track.getMotions() + assert len(motions) == len(motions_before) + 1 + + ( + motion_id, + _, + _, + motion_duration, + motion_start_date, + _, + _, + _, + _, + ) = motions[-1] + + chain.mine(1, motion_start_date + motion_duration + 1) + + # bring fresh report for vault + current_time = chain.time() + accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) + lazy_oracle.updateReportData( + current_time, + 1000, + "0x00", + "0x00", + {"from": accounting_oracle}) + + # make vault unhealthy + lazy_oracle_account = accounts.at(LAZY_ORACLE, force=True) + vault_hub.applyVaultReport( + vault, + current_time, + initial_total_value, + initial_total_value, + 4 * initial_total_value, + 0, + 0, + 0, + {"from": lazy_oracle_account}) + + tx = easy_track.enactMotion( + motion_id, + tx.events["MotionCreated"]["_evmScriptCallData"], + {"from": stranger}, + ) + + # Check event was emitted + assert len(tx.events["ForcedValidatorExitTriggered"]) == 1 + event = tx.events["ForcedValidatorExitTriggered"][0] + assert event["vault"] == vault + assert event["pubkeys"] == "0x" + pubkey.hex() + assert event["refundRecipient"] == VAULTS_ADAPTER + + +def test_socialize_bad_debt_in_vault_hub(easy_track, trusted_address, stranger, operator_grid, lazy_oracle, vault_hub, vault_factory): + + initial_total_value = 2 * 10**18 + max_shares_to_socialize = 2 * 10**16 + + # Enable minting in default group + executor = accounts.at(EASYTRACK_EVMSCRIPT_EXECUTOR, force=True) + operator_grid.alterTiers([0], [(100_000 * 10**18, 300, 250, 50, 40, 10)], {"from": executor}) + + # First create the vaults + creation_tx = vault_factory.createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": initial_total_value}, + ) + bad_debt_vault = creation_tx.events["VaultCreated"][0]["vault"] + + # Fresh report for bad debt vault + current_time = chain.time() + accounting_oracle = accounts.at(ACCOUNTING_ORACLE, force=True) + lazy_oracle.updateReportData(current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) + lazy_oracle_account = accounts.at(LAZY_ORACLE, force=True) + vault_hub.applyVaultReport( + bad_debt_vault, + current_time, + initial_total_value, + initial_total_value, + 0, + 0, + 0, + 0, + {"from": lazy_oracle_account}) + + bad_debt_dashboard = accounts.at(creation_tx.events["DashboardCreated"][0]["dashboard"], force=True) + vault_hub.mintShares(bad_debt_vault, stranger, 10 * max_shares_to_socialize, {"from": bad_debt_dashboard}) + + creation_tx = vault_factory.createVaultWithDashboard( + stranger, + stranger, + stranger, + 100, + 3600, # 1 hour + [], + {"from": stranger, "value": initial_total_value}, + ) + vault_acceptor = creation_tx.events["VaultCreated"][0]["vault"] + + calldata = _encode_calldata(["address[]", "address[]", "uint256[]"], [[bad_debt_vault], [vault_acceptor], [max_shares_to_socialize]]) + + motions_before = easy_track.getMotions() + tx = easy_track.createMotion(SOCIALIZE_BAD_DEBT_IN_VAULT_HUB_FACTORY, calldata, {"from": trusted_address}) + motions = easy_track.getMotions() + assert len(motions) == len(motions_before) + 1 + + ( + motion_id, + _, + _, + motion_duration, + motion_start_date, + _, + _, + _, + _, + ) = motions[-1] + + chain.mine(1, motion_start_date + motion_duration + 1) + + # Bring fresh report for vaults + current_time = chain.time() + lazy_oracle.updateReportData(current_time, 1000, "0x00", "0x00", {"from": accounting_oracle}) + + # Fresh report for acceptor vault + vault_hub.applyVaultReport( + vault_acceptor, + current_time, + initial_total_value, + initial_total_value, + 0, + 0, + 0, + 0, + {"from": lazy_oracle_account}) + + # Make bad debt on second vault + vault_hub.applyVaultReport( + bad_debt_vault, + current_time, + 10 * max_shares_to_socialize, + initial_total_value, + 0, + initial_total_value, + 0, + 0, + {"from": lazy_oracle_account}) + + bad_debt_record_before = vault_hub.vaultRecord(bad_debt_vault) + bad_liability_before = bad_debt_record_before[2] + acceptor_record_before = vault_hub.vaultRecord(vault_acceptor) + acceptor_liability_before = acceptor_record_before[2] + + tx = easy_track.enactMotion( + motion_id, + tx.events["MotionCreated"]["_evmScriptCallData"], + {"from": stranger}, + ) + + bad_debt_record_after = vault_hub.vaultRecord(bad_debt_vault) + bad_liability_after = bad_debt_record_after[2] + acceptor_record_after = vault_hub.vaultRecord(vault_acceptor) + acceptor_liability_after = acceptor_record_after[2] + + assert bad_liability_after == bad_liability_before - max_shares_to_socialize + assert acceptor_liability_after == acceptor_liability_before + max_shares_to_socialize + + # Check that events were emitted for failed socializations + assert len(tx.events["BadDebtSocialized"]) == 1 + event = tx.events["BadDebtSocialized"][0] + assert event["vaultDonor"] == bad_debt_vault + assert event["vaultAcceptor"] == vault_acceptor + assert event["badDebtShares"] == max_shares_to_socialize diff --git a/tests/utils_test_2025_12_15_operations.py b/tests/utils_test_2025_12_15_operations.py new file mode 100644 index 000000000..98ec2e6ae --- /dev/null +++ b/tests/utils_test_2025_12_15_operations.py @@ -0,0 +1,1245 @@ +from typing import List, NamedTuple + +from brownie import chain, interface, reverts, accounts, ZERO_ADDRESS, convert, web3 +from brownie.network.transaction import TransactionReceipt +from utils.permission_parameters import Param, SpecialArgumentID, encode_argument_value_if, ArgumentValue, Op +from utils.test.easy_track_helpers import create_and_enact_payment_motion +from utils.test.event_validators.staking_router import validate_staking_module_update_event, StakingModuleItem +from utils.evm_script import encode_call_script +from utils.voting import find_metadata_by_vote_id +from utils.agent import agent_forward +from utils.ipfs import get_lido_vote_cid_from_str +from utils.dual_governance import PROPOSAL_STATUS +from utils.test.event_validators.allowed_tokens_registry import validate_add_token_event +from utils.test.event_validators.dual_governance import validate_dual_governance_submit_event +from utils.test.tx_tracing_helpers import ( + group_voting_events_from_receipt, + group_dg_events_from_receipt, + count_vote_items_by_events, + display_voting_events, + display_dg_events +) +from utils.allowed_recipients_registry import set_limit_parameters +from utils.test.event_validators.payout import ( + validate_token_payout_event, + Payout, +) +from utils.test.event_validators.allowed_recipients_registry import ( + validate_set_limit_parameter_event, +) +from utils.test.event_validators.permission import ( + validate_grant_role_event, + validate_revoke_role_event, + Permission, + validate_permission_grantp_event, + validate_permission_revoke_event, +) +from utils.test.event_validators.node_operators_registry import ( + validate_target_validators_count_changed_event, + TargetValidatorsCountChanged, +) + + +class TokenLimit(NamedTuple): + address: str + limit: int + + +# ============================== Import vote ================================= +from scripts.vote_2025_12_15 import start_vote, get_vote_items + + +# ============================== Addresses =================================== +VOTING = "0x2e59A20f205bB85a89C53f1936454680651E618e" +AGENT = "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" +EMERGENCY_PROTECTED_TIMELOCK = "0xCE0425301C85c5Ea2A0873A2dEe44d78E02D2316" +DUAL_GOVERNANCE = "0xC1db28B3301331277e307FDCfF8DE28242A4486E" +DUAL_GOVERNANCE_ADMIN_EXECUTOR = "0x23E0B465633FF5178808F4A75186E2F2F9537021" +ET_TRP_REGISTRY = "0x231Ac69A1A37649C6B06a71Ab32DdD92158C80b8" +STAKING_ROUTER = "0xFdDf38947aFB03C621C71b06C9C70bce73f12999" +ET_EVM_SCRIPT_EXECUTOR = "0xFE5986E06210aC1eCC1aDCafc0cc7f8D63B3F977" +DEPOSIT_SECURITY_MODULE = "0xffa96d84def2ea035c7ab153d8b991128e3d72fd" +EASY_TRACK = "0xF0211b7660680B49De1A7E9f25C65660F0a13Fea" +FINANCE = "0xB9E5CBB9CA5b0d659238807E84D0176930753d86" +ACL = "0x9895f0f17cc1d1891b6f18ee0b483b6f221b37bb" +CURATED_MODULE = "0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5" + +TRP_COMMITTEE = "0x834560F580764Bc2e0B16925F8bF229bb00cB759" +TRP_TOP_UP_EVM_SCRIPT_FACTORY = "0xBd2b6dC189EefD51B273F5cb2d99BA1ce565fb8C" + +STABLECOINS_ALLOWED_TOKENS_REGISTRY = "0x4AC40c34f8992bb1e5E856A448792158022551ca" +LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0xE1f6BaBb445F809B97e3505Ea91749461050F780" +LIDO_LABS_TRUSTED_CALLER = "0x95B521B4F55a447DB89f6a27f951713fC2035f3F" +LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY = "0x68267f3D310E9f0FF53a37c141c90B738E1133c2" + +LEGO_LDO_TRUSTED_CALLER = "0x12a43b049A7D330cB8aEAB5113032D18AE9a9030" +LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x00caAeF11EC545B192f16313F53912E453c91458" +LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY = "0x97615f72c3428A393d65A84A3ea6BBD9ad6C0D74" + +GAS_SUPPLY_STETH_TRUSTED_CALLER = "0x5181d5D56Af4f823b96FE05f062D7a09761a5a53" +GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY = "0x200dA0b6a9905A377CF8D469664C65dB267009d1" +GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY = "0x49d1363016aA899bba09ae972a1BF200dDf8C55F" +GAS_SUPPLY_STETH_SPENDABLE_BALANCE = 1_000 * 10**18 + +LOL_MS = "0x87D93d9B2C672bf9c9642d853a8682546a5012B5" +SDVT = "0xaE7B191A31f627b4eB1d4DaC64eaB9976995b433" +PSM_VARIANT1_ACTIONS = "0xd0A61F2963622e992e6534bde4D52fd0a89F39E0" + + +# ============================== Roles =================================== +CREATE_PAYMENTS_ROLE = "CREATE_PAYMENTS_ROLE" +ADD_TOKEN_TO_ALLOWED_LIST_ROLE = "ADD_TOKEN_TO_ALLOWED_LIST_ROLE" +REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE = "REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE" + + +# ============================== Constants =================================== +CURATED_MODULE_ID = 1 +CURATED_MODULE_TARGET_SHARE_BP = 10000 +CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP = 10000 +CURATED_MODULE_OLD_MODULE_FEE_BP = 500 +CURATED_MODULE_NEW_MODULE_FEE_BP = 350 +CURATED_MODULE_OLD_TREASURY_FEE_BP = 500 +CURATED_MODULE_NEW_TREASURY_FEE_BP = 650 +CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +CURATED_MODULE_NAME = "curated-onchain-v1" + +SDVT_MODULE_ID = 2 +SDVT_MODULE_OLD_TARGET_SHARE_BP = 400 +SDVT_MODULE_NEW_TARGET_SHARE_BP = 430 +SDVT_MODULE_OLD_PRIORITY_EXIT_THRESHOLD_BP = 444 +SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP = 478 +SDVT_MODULE_MODULE_FEE_BP = 800 +SDVT_MODULE_TREASURY_FEE_BP = 200 +SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK = 150 +SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 +SDVT_MODULE_NAME = "SimpleDVT" + +MATIC_IN_TREASURY_BEFORE = 508_106_165_781_175_837_137_177 +MATIC_IN_TREASURY_AFTER = 165_781_175_837_137_177 +MATIC_IN_LIDO_LABS_BEFORE = 0 +MATIC_IN_LIDO_LABS_AFTER = 508_106 * 10**18 + +TRP_LIMIT_BEFORE = 9_178_284_420 * 10**15 # == 9_178_284.42 * 10**18 +TRP_LIMIT_AFTER = 15_000_000 * 10**18 +TRP_PERIOD_START_TIMESTAMP = 1735689600 # January 1, 2025 UTC +TRP_PERIOD_END_TIMESTAMP = 1767225600 # January 1, 2026 UTC +TRP_PERIOD_DURATION_MONTHS = 12 + +def get_trp_period_start(): + period_start_timestamp_for_trp = TRP_PERIOD_START_TIMESTAMP + if chain.time() >= 1767225600: # Thu Jan 01 2026 00:00:00 GMT+0000 + period_start_timestamp_for_trp = 1767225600 # Thu Jan 01 2026 00:00:00 GMT+0000 + return period_start_timestamp_for_trp + +def get_trp_period_end(): + period_end_timestamp_for_trp = TRP_PERIOD_END_TIMESTAMP + if chain.time() >= 1767225600: # Thu Jan 01 2026 00:00:00 GMT+0000 + period_end_timestamp_for_trp = 1798761600 # Fri Jan 01 2027 00:00:00 GMT+0000 + return period_end_timestamp_for_trp + +ALLOWED_TOKENS_BEFORE = 3 +ALLOWED_TOKENS_AFTER = 4 + +A41_NO_ID = 32 +NO_TARGET_LIMIT_MODE_BEFORE = 0 +NO_TARGET_LIMIT_MODE_AFTER = 1 +NEW_A41_TARGET_LIMIT = 0 +A41_TARGET_CHANGE_REQUEST = TargetValidatorsCountChanged( + nodeOperatorId=A41_NO_ID, + targetValidatorsCount=NEW_A41_TARGET_LIMIT, + targetLimitMode=NO_TARGET_LIMIT_MODE_AFTER, +) + + +# ============================== Tokens =================================== +MATIC_TOKEN = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0" +LDO_TOKEN = "0x5a98fcbea516cf06857215779fd812ca3bef1b32" +STETH_TOKEN = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" +WSTETH_TOKEN = "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0" +SUSDS_TOKEN = "0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD" +USDC_TOKEN = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +USDT_TOKEN = "0xdac17f958d2ee523a2206206994597c13d831ec7" +DAI_TOKEN = "0x6b175474e89094c44da98b954eedeac495271d0f" + + +# ============================== Finance Limits =================================== +AMOUNT_LIMITS_LEN_BEFORE = 19 +def amount_limits_before() -> List[Param]: + ldo_limit = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) + eth_limit = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) + steth_limit = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) + dai_limit = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) + usdc_limit = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) + usdt_limit = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) + + token_arg_index = 0 + amount_arg_index = 2 + + limits = [ + # 0: if (1) then (2) else (3) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) + ), + # 1: (_token == stETH) + Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit.address)), + # 2: { return _amount <= 1_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit.limit)), + # + # 3: else if (4) then (5) else (6) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) + ), + # 4: (_token == DAI) + Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit.address)), + # 5: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit.limit)), + # + # 6: else if (7) then (8) else (9) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) + ), + # 7: (_token == LDO) + Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit.address)), + # 8: { return _amount <= 5_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit.limit)), + # + # 9: else if (10) then (11) else (12) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=10, success=11, failure=12), + ), + # 10: (_token == USDC) + Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit.address)), + # 11: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit.limit)), + # + # 12: else if (13) then (14) else (15) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=13, success=14, failure=15), + ), + # 13: (_token == USDT) + Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit.address)), + # 14: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit.limit)), + # + # 15: else if (16) then (17) else (18) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=16, success=17, failure=18), + ), + # 16: (_token == ETH) + Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit.address)), + # 17: { return _amount <= 1000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit.limit)), + # + # 18: else { return false } + Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), + ] + + assert len(limits) == AMOUNT_LIMITS_LEN_BEFORE + + return limits + +AMOUNT_LIMITS_LEN_AFTER = 22 +ldo_limit_after = TokenLimit(LDO_TOKEN, 5_000_000 * (10**18)) +eth_limit_after = TokenLimit(ZERO_ADDRESS, 1_000 * 10**18) +steth_limit_after = TokenLimit(STETH_TOKEN, 1_000 * (10**18)) +dai_limit_after = TokenLimit(DAI_TOKEN, 2_000_000 * (10**18)) +usdc_limit_after = TokenLimit(USDC_TOKEN, 2_000_000 * (10**6)) +usdt_limit_after = TokenLimit(USDT_TOKEN, 2_000_000 * (10**6)) +susds_limit_after = TokenLimit(SUSDS_TOKEN, 2_000_000 * (10**18)) +def amount_limits_after() -> List[Param]: + + token_arg_index = 0 + amount_arg_index = 2 + + limits = [ + # 0: if (1) then (2) else (3) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=1, success=2, failure=3) + ), + # 1: (_token == stETH) + Param(token_arg_index, Op.EQ, ArgumentValue(steth_limit_after.address)), + # 2: { return _amount <= 1_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(steth_limit_after.limit)), + # + # 3: else if (4) then (5) else (6) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=4, success=5, failure=6) + ), + # 4: (_token == DAI) + Param(token_arg_index, Op.EQ, ArgumentValue(dai_limit_after.address)), + # 5: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(dai_limit_after.limit)), + # + # 6: else if (7) then (8) else (9) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, Op.IF_ELSE, encode_argument_value_if(condition=7, success=8, failure=9) + ), + # 7: (_token == LDO) + Param(token_arg_index, Op.EQ, ArgumentValue(ldo_limit_after.address)), + # 8: { return _amount <= 5_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(ldo_limit_after.limit)), + # + # 9: else if (10) then (11) else (12) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=10, success=11, failure=12), + ), + # 10: (_token == USDC) + Param(token_arg_index, Op.EQ, ArgumentValue(usdc_limit_after.address)), + # 11: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdc_limit_after.limit)), + # + # 12: else if (13) then (14) else (15) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=13, success=14, failure=15), + ), + # 13: (_token == USDT) + Param(token_arg_index, Op.EQ, ArgumentValue(usdt_limit_after.address)), + # 14: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(usdt_limit_after.limit)), + # + # 15: else if (16) then (17) else (18) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=16, success=17, failure=18), + ), + # 16: (_token == ETH) + Param(token_arg_index, Op.EQ, ArgumentValue(eth_limit_after.address)), + # 17: { return _amount <= 1000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(eth_limit_after.limit)), + # + # 18: else if (19) then (20) else (21) + Param( + SpecialArgumentID.LOGIC_OP_PARAM_ID, + Op.IF_ELSE, + encode_argument_value_if(condition=19, success=20, failure=21), + ), + # 19: (_token == sUSDS) + Param(token_arg_index, Op.EQ, ArgumentValue(susds_limit_after.address)), + # 20: { return _amount <= 2_000_000 } + Param(amount_arg_index, Op.LTE, ArgumentValue(susds_limit_after.limit)), + # + # 21: else { return false } + Param(SpecialArgumentID.PARAM_VALUE_PARAM_ID, Op.RET, ArgumentValue(0)), + ] + + # Verify that the first part of the after_limits matches the before_limits + for i in range(AMOUNT_LIMITS_LEN_BEFORE - 1): + assert limits[i].id == amount_limits_before()[i].id + assert limits[i].op.value == amount_limits_before()[i].op.value + assert limits[i].value == amount_limits_before()[i].value + + assert len(limits) == AMOUNT_LIMITS_LEN_AFTER + + return limits + + +def dual_governance_proposal_calls(): + + staking_router = interface.StakingRouter(STAKING_ROUTER) + + dg_items = [ + agent_forward([ + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + CURATED_MODULE_ID, + CURATED_MODULE_TARGET_SHARE_BP, + CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP, + CURATED_MODULE_NEW_MODULE_FEE_BP, + CURATED_MODULE_NEW_TREASURY_FEE_BP, + CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK, + CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), + agent_forward([ + ( + staking_router.address, + staking_router.updateStakingModule.encode_input( + SDVT_MODULE_ID, + SDVT_MODULE_NEW_TARGET_SHARE_BP, + SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP, + SDVT_MODULE_MODULE_FEE_BP, + SDVT_MODULE_TREASURY_FEE_BP, + SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK, + SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, + ), + ), + ]), + agent_forward([ + ( + staking_router.address, + staking_router.updateTargetValidatorsLimits.encode_input(CURATED_MODULE_ID, A41_NO_ID, NO_TARGET_LIMIT_MODE_AFTER, NEW_A41_TARGET_LIMIT), + ) + ]), + agent_forward([ + set_limit_parameters( + limit=TRP_LIMIT_AFTER, + period_duration_months=TRP_PERIOD_DURATION_MONTHS, + registry_address=ET_TRP_REGISTRY, + ), + ]), + ] + + # Convert each dg_item to the expected format + proposal_calls = [] + for dg_item in dg_items: + target, data = dg_item # agent_forward returns (target, data) + proposal_calls.append({ + "target": target, + "value": 0, + "data": data + }) + + return proposal_calls + + +def enact_and_test_voting( + helpers, + accounts, + ldo_holder, + vote_ids_from_env, + stranger, + EXPECTED_VOTE_ID, + EXPECTED_DG_PROPOSAL_ID, +): + EXPECTED_VOTE_EVENTS_COUNT = 7 + IPFS_DESCRIPTION_HASH = "bafkreieptki4mrkhpd22ij3cym777l4iivrdknwtglchdtvptujz2dgn7u" + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + voting = interface.Voting(VOTING) + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + matic_token = interface.ERC20(MATIC_TOKEN) + acl = interface.ACL(ACL) + stablecoins_allowed_tokens_registry = interface.AllowedTokensRegistry(STABLECOINS_ALLOWED_TOKENS_REGISTRY) + + # ========================================================================= + # ======================== Identify or Create vote ======================== + # ========================================================================= + if vote_ids_from_env: + vote_id = vote_ids_from_env[0] + if EXPECTED_VOTE_ID is not None: + assert vote_id == EXPECTED_VOTE_ID + elif EXPECTED_VOTE_ID is not None and voting.votesLength() > EXPECTED_VOTE_ID: + vote_id = EXPECTED_VOTE_ID + else: + vote_id, _ = start_vote({"from": ldo_holder}, silent=True) + + _, call_script_items = get_vote_items() + onchain_script = voting.getVote(vote_id)["script"] + assert onchain_script == encode_call_script(call_script_items) + + # ========================================================================= + # ============================= Execute Vote ============================== + # ========================================================================= + is_executed = voting.getVote(vote_id)["executed"] + if not is_executed: + # ======================================================================= + # ========================= Before voting checks ======================== + # ======================================================================= + + # Item 1 is DG - skipped here + + # Items 2,4 + assert not stablecoins_allowed_tokens_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING + ) + + # Item 3 + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() + assert len(allowed_tokens_before) == ALLOWED_TOKENS_BEFORE + assert allowed_tokens_before[0] == DAI_TOKEN + assert allowed_tokens_before[1] == USDT_TOKEN + assert allowed_tokens_before[2] == USDC_TOKEN + + # Items 5,6 + assert acl.getPermissionParamsLength( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)) + ) == AMOUNT_LIMITS_LEN_BEFORE + for i in range(AMOUNT_LIMITS_LEN_BEFORE): + id, op, val = acl.getPermissionParam( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)), + i + ) + assert id == amount_limits_before()[i].id + assert op == amount_limits_before()[i].op.value + assert val == amount_limits_before()[i].value + + # Item 7 + matic_treasury_balance_before = matic_token.balanceOf(agent.address) + assert matic_treasury_balance_before == MATIC_IN_TREASURY_BEFORE + matic_labs_balance_before = matic_token.balanceOf(LOL_MS) + assert matic_labs_balance_before == MATIC_IN_LIDO_LABS_BEFORE + + assert get_lido_vote_cid_from_str(find_metadata_by_vote_id(vote_id)) == IPFS_DESCRIPTION_HASH + + vote_tx: TransactionReceipt = helpers.execute_vote(vote_id=vote_id, accounts=accounts, dao_voting=voting) + display_voting_events(vote_tx) + vote_events = group_voting_events_from_receipt(vote_tx) + + # ======================================================================= + # ========================= After voting checks ========================= + # ======================================================================= + + # Item 1 is DG - skipped here + + # Items 2,4 + assert not stablecoins_allowed_tokens_registry.hasRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING + ) + + # Item 3 + assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + allowed_tokens_before = stablecoins_allowed_tokens_registry.getAllowedTokens() + assert len(allowed_tokens_before) == ALLOWED_TOKENS_AFTER + assert allowed_tokens_before[0] == DAI_TOKEN + assert allowed_tokens_before[1] == USDT_TOKEN + assert allowed_tokens_before[2] == USDC_TOKEN + assert allowed_tokens_before[3] == SUSDS_TOKEN + + # Items 5,6 + assert acl.getPermissionParamsLength( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)) + ) == AMOUNT_LIMITS_LEN_AFTER + for i in range(AMOUNT_LIMITS_LEN_AFTER): + id, op, val = acl.getPermissionParam( + ET_EVM_SCRIPT_EXECUTOR, + FINANCE, + convert.to_uint(web3.keccak(text=CREATE_PAYMENTS_ROLE)), + i + ) + assert id == amount_limits_after()[i].id + assert op == amount_limits_after()[i].op.value + assert val == amount_limits_after()[i].value + + # check Finance create payment permissions with limits for all allowed tokens + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(susds_limit_after.address), convert.to_uint(stranger.address), susds_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdt_limit_after.address), convert.to_uint(stranger.address), usdt_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdt_limit_after.address), convert.to_uint(stranger.address), usdt_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdc_limit_after.address), convert.to_uint(stranger.address), usdc_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(usdc_limit_after.address), convert.to_uint(stranger.address), usdc_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(dai_limit_after.address), convert.to_uint(stranger.address), dai_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(steth_limit_after.address), convert.to_uint(stranger.address), steth_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(eth_limit_after.address), convert.to_uint(stranger.address), eth_limit_after.limit + 1], + ) + + assert acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit], + ) + assert not acl.hasPermission["address,address,bytes32,uint[]"]( + ET_EVM_SCRIPT_EXECUTOR, FINANCE, web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + [convert.to_uint(ldo_limit_after.address), convert.to_uint(stranger.address), ldo_limit_after.limit + 1], + ) + + # Item 7 + matic_treasury_balance_after = matic_token.balanceOf(agent.address) + assert matic_treasury_balance_after == MATIC_IN_TREASURY_AFTER + matic_labs_balance_after = matic_token.balanceOf(LOL_MS) + assert matic_labs_balance_after == MATIC_IN_LIDO_LABS_AFTER + # make sure LOL can actually spend the received MATIC + matic_token.transfer(stranger.address, MATIC_IN_LIDO_LABS_AFTER / 2, {"from": LOL_MS}) + assert matic_token.balanceOf(LOL_MS) == MATIC_IN_LIDO_LABS_AFTER / 2 + assert matic_token.balanceOf(stranger.address) == MATIC_IN_LIDO_LABS_AFTER / 2 + + assert len(vote_events) == EXPECTED_VOTE_EVENTS_COUNT + assert count_vote_items_by_events(vote_tx, voting.address) == EXPECTED_VOTE_EVENTS_COUNT + if EXPECTED_DG_PROPOSAL_ID is not None: + assert EXPECTED_DG_PROPOSAL_ID == timelock.getProposalsCount() + + # validate DG Proposal Submit event + validate_dual_governance_submit_event( + vote_events[0], + proposal_id=EXPECTED_DG_PROPOSAL_ID, + proposer=VOTING, + executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + metadata="Change Curated Module fees, raise SDVT stake share limit, set A41 soft target validator limit to 0, set Easy Track TRP limit", + proposal_calls=dual_governance_proposal_calls(), + ) + + # validate all other voting events + validate_grant_role_event( + events=vote_events[1], + role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), + grant_to=VOTING, + sender=VOTING, + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, + ) + validate_add_token_event( + event=vote_events[2], + token=SUSDS_TOKEN, + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY + ) + validate_revoke_role_event( + events=vote_events[3], + role=web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE).hex(), + revoke_from=VOTING, + sender=VOTING, + emitted_by=STABLECOINS_ALLOWED_TOKENS_REGISTRY, + ) + validate_permission_revoke_event( + event=vote_events[4], + p=Permission( + app=FINANCE, + entity=ET_EVM_SCRIPT_EXECUTOR, + role=web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + ), + emitted_by=ACL, + ) + validate_permission_grantp_event( + event=vote_events[5], + p=Permission( + app=FINANCE, + entity=ET_EVM_SCRIPT_EXECUTOR, + role=web3.keccak(text=CREATE_PAYMENTS_ROLE).hex(), + ), + params=amount_limits_after(), + emitted_by=ACL, + ) + validate_token_payout_event( + event=vote_events[6], + p=Payout( + token_addr=MATIC_TOKEN, + from_addr=AGENT, + to_addr=LOL_MS, + amount=MATIC_IN_LIDO_LABS_AFTER), + is_steth=False, + emitted_by=AGENT + ) + + # ======================================================================= + # =========================== Scenario tests ============================ + # ======================================================================= + + # put a lot of tokens into Agent to check Finance/ET limits + prepare_agent_for_dai_payment(30_000_000 * 10**18) + prepare_agent_for_usdt_payment(30_000_000 * 10**6) + prepare_agent_for_usdc_payment(30_000_000 * 10**6) + prepare_agent_for_susds_payment(30_000_000 * 10**18) + prepare_agent_for_ldo_payment(10_000_000 * 10**18) + prepare_agent_for_steth_payment(2_000 * 10**18) + + # check ET limits via Easy Track motion + ET_LIDO_LABS_STABLES_LIMIT = interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).getLimitParameters({"from": AGENT})[0] // 10**18 + LEGO_LDO_SPENDABLE_BALANCE = interface.AllowedRecipientRegistry(LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY).getLimitParameters({"from": AGENT})[0] + et_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, ET_LIDO_LABS_STABLES_LIMIT * 10**6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, LEGO_LDO_SPENDABLE_BALANCE, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + et_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, GAS_SUPPLY_STETH_SPENDABLE_BALANCE, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY) + + # check Finance limits via Easy Track motion + finance_limit_test(stranger, interface.ERC20(SUSDS_TOKEN), susds_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(USDC_TOKEN), usdc_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(DAI_TOKEN), dai_limit_after.limit, 18, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(USDT_TOKEN), usdt_limit_after.limit, 6, LIDO_LABS_TRUSTED_CALLER, LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(LDO_TOKEN), ldo_limit_after.limit, 18, LEGO_LDO_TRUSTED_CALLER, LEGO_LDO_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, LEGO_LDO_ALLOWED_RECIPIENTS_REGISTRY) + finance_limit_test(stranger, interface.ERC20(STETH_TOKEN), steth_limit_after.limit, 18, GAS_SUPPLY_STETH_TRUSTED_CALLER, GAS_SUPPLY_STETH_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, GAS_SUPPLY_STETH_ALLOWED_RECIPIENTS_REGISTRY) + + # sUSDS can be removed after being added to the allowed list + chain.snapshot() + stablecoins_allowed_tokens_registry.grantRole( + convert.to_uint(web3.keccak(text=REMOVE_TOKEN_FROM_ALLOWED_LIST_ROLE)), + VOTING, + {"from": VOTING} + ) + assert stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + stablecoins_allowed_tokens_registry.removeToken( + SUSDS_TOKEN, + {"from": VOTING} + ) + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(SUSDS_TOKEN) + with reverts("TOKEN_NOT_ALLOWED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(SUSDS_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # spending tokens not from the allowed list should fail + chain.snapshot() + with reverts("TOKEN_NOT_ALLOWED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(WSTETH_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # spending the allowed token not from the Finance CREATE_PAYMENTS_ROLE's list should fail + chain.snapshot() + stablecoins_allowed_tokens_registry.grantRole( + convert.to_uint(web3.keccak(text=ADD_TOKEN_TO_ALLOWED_LIST_ROLE)), + VOTING, + {"from": VOTING} + ) + assert not stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + stablecoins_allowed_tokens_registry.addToken( + WSTETH_TOKEN, + {"from": VOTING} + ) + assert stablecoins_allowed_tokens_registry.isTokenAllowed(WSTETH_TOKEN) + with reverts("APP_AUTH_FAILED"): + create_and_enact_payment_motion( + interface.EasyTrack(EASY_TRACK), + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + interface.ERC20(WSTETH_TOKEN), + [accounts.at(LIDO_LABS_TRUSTED_CALLER, force=True)], + [1 * 10**18], + stranger, + ) + chain.revert() + + # happy path + usds_wrap_happy_path(stranger) + + +def enact_and_test_dg(stranger, EXPECTED_DG_PROPOSAL_ID): + EXPECTED_DG_EVENTS_COUNT = 4 + + # ======================================================================= + # ========================= Arrange variables =========================== + # ======================================================================= + agent = interface.Agent(AGENT) + timelock = interface.EmergencyProtectedTimelock(EMERGENCY_PROTECTED_TIMELOCK) + dual_governance = interface.DualGovernance(DUAL_GOVERNANCE) + staking_router = interface.StakingRouter(STAKING_ROUTER) + et_trp_registry = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY) + curated_module = interface.NodeOperatorsRegistry(CURATED_MODULE) + + curated_module_before = None + sdvt_module_before = None + a41_summary_before = None + TRP_ALREADY_SPENT_AFTER = None + + if EXPECTED_DG_PROPOSAL_ID is not None: + details = timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID) + if details["status"] != PROPOSAL_STATUS["executed"]: + # ========================================================================= + # ================== DG before proposal executed checks =================== + # ========================================================================= + + # Item 1.1 + curated_module_before = staking_router.getStakingModule(CURATED_MODULE_ID) + assert curated_module_before['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP + assert curated_module_before['id'] == CURATED_MODULE_ID + assert curated_module_before['priorityExitShareThreshold'] == CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert curated_module_before['stakingModuleFee'] == CURATED_MODULE_OLD_MODULE_FEE_BP + assert curated_module_before['treasuryFee'] == CURATED_MODULE_OLD_TREASURY_FEE_BP + assert curated_module_before['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK + assert curated_module_before['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert curated_module_before['name'] == CURATED_MODULE_NAME + + # Item 1.2 + sdvt_module_before = staking_router.getStakingModule(SDVT_MODULE_ID) + assert sdvt_module_before['stakeShareLimit'] == SDVT_MODULE_OLD_TARGET_SHARE_BP + assert sdvt_module_before['id'] == SDVT_MODULE_ID + assert sdvt_module_before['priorityExitShareThreshold'] == SDVT_MODULE_OLD_PRIORITY_EXIT_THRESHOLD_BP + assert sdvt_module_before['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + assert sdvt_module_before['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + assert sdvt_module_before['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + assert sdvt_module_before['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert sdvt_module_before['name'] == SDVT_MODULE_NAME + + # Item 1.3 + a41_summary_before = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) + assert a41_summary_before['targetLimitMode'] == NO_TARGET_LIMIT_MODE_BEFORE + assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" + + # Items 1.4 + trp_limit_before, trp_period_duration_months_before = et_trp_registry.getLimitParameters() + trp_already_spent_amount_before, trp_spendable_balance_before, trp_period_start_before, trp_period_end_before = et_trp_registry.getPeriodState() + assert trp_limit_before == TRP_LIMIT_BEFORE + assert trp_period_duration_months_before == TRP_PERIOD_DURATION_MONTHS + assert trp_spendable_balance_before == TRP_LIMIT_BEFORE - trp_already_spent_amount_before + TRP_ALREADY_SPENT_AFTER = trp_already_spent_amount_before # TRP_ALREADY_SPENT_AFTER must be the same as before the execution + assert trp_period_start_before == TRP_PERIOD_START_TIMESTAMP + assert trp_period_end_before == TRP_PERIOD_END_TIMESTAMP + + if details["status"] == PROPOSAL_STATUS["submitted"]: + chain.sleep(timelock.getAfterSubmitDelay() + 1) + dual_governance.scheduleProposal(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + + if timelock.getProposalDetails(EXPECTED_DG_PROPOSAL_ID)["status"] == PROPOSAL_STATUS["scheduled"]: + chain.sleep(timelock.getAfterScheduleDelay() + 1) + dg_tx: TransactionReceipt = timelock.execute(EXPECTED_DG_PROPOSAL_ID, {"from": stranger}) + display_dg_events(dg_tx) + dg_events = group_dg_events_from_receipt( + dg_tx, + timelock=EMERGENCY_PROTECTED_TIMELOCK, + admin_executor=DUAL_GOVERNANCE_ADMIN_EXECUTOR, + ) + assert count_vote_items_by_events(dg_tx, agent.address) == EXPECTED_DG_EVENTS_COUNT + assert len(dg_events) == EXPECTED_DG_EVENTS_COUNT + + # validate all DG events + validate_staking_module_update_event( + event=dg_events[0], + module_item=StakingModuleItem( + id=CURATED_MODULE_ID, + name=CURATED_MODULE_NAME, + address=None, + target_share=CURATED_MODULE_TARGET_SHARE_BP, + module_fee=CURATED_MODULE_NEW_MODULE_FEE_BP, + treasury_fee=CURATED_MODULE_NEW_TREASURY_FEE_BP, + priority_exit_share=CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP), + emitted_by=STAKING_ROUTER + ) + validate_staking_module_update_event( + event=dg_events[1], + module_item=StakingModuleItem( + id=SDVT_MODULE_ID, + name=SDVT_MODULE_NAME, + address=None, + target_share=SDVT_MODULE_NEW_TARGET_SHARE_BP, + module_fee=SDVT_MODULE_MODULE_FEE_BP, + treasury_fee=SDVT_MODULE_TREASURY_FEE_BP, + priority_exit_share=SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP), + emitted_by=STAKING_ROUTER + ) + validate_target_validators_count_changed_event( + event=dg_events[2], + t=A41_TARGET_CHANGE_REQUEST, + emitted_by=CURATED_MODULE, + ) + validate_set_limit_parameter_event( + dg_events[3], + limit=TRP_LIMIT_AFTER, + period_duration_month=TRP_PERIOD_DURATION_MONTHS, + period_start_timestamp=get_trp_period_start(), + emitted_by=ET_TRP_REGISTRY, + ) + + # ========================================================================= + # ==================== After DG proposal executed checks ================== + # ========================================================================= + + # Item 1.1 + curated_module_after = staking_router.getStakingModule(CURATED_MODULE_ID) + assert curated_module_after['stakingModuleFee'] == CURATED_MODULE_NEW_MODULE_FEE_BP + assert curated_module_after['treasuryFee'] == CURATED_MODULE_NEW_TREASURY_FEE_BP + assert curated_module_after['id'] == CURATED_MODULE_ID + assert curated_module_after['stakeShareLimit'] == CURATED_MODULE_TARGET_SHARE_BP + assert curated_module_after['priorityExitShareThreshold'] == CURATED_MODULE_PRIORITY_EXIT_THRESHOLD_BP + assert curated_module_after['maxDepositsPerBlock'] == CURATED_MODULE_MAX_DEPOSITS_PER_BLOCK + assert curated_module_after['minDepositBlockDistance'] == CURATED_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert curated_module_after['name'] == CURATED_MODULE_NAME + # additional checks to make sure no other fields were changed (if before state is available) + if curated_module_before is not None: + assert curated_module_after['id'] == curated_module_before['id'] + assert curated_module_after['stakingModuleAddress'] == curated_module_before['stakingModuleAddress'] + assert curated_module_after['stakeShareLimit'] == curated_module_before['stakeShareLimit'] + assert curated_module_after['status'] == curated_module_before['status'] + assert curated_module_after['name'] == curated_module_before['name'] + assert curated_module_after['lastDepositAt'] == curated_module_before['lastDepositAt'] + assert curated_module_after['lastDepositBlock'] == curated_module_before['lastDepositBlock'] + assert curated_module_after['exitedValidatorsCount'] == curated_module_before['exitedValidatorsCount'] + assert curated_module_after['maxDepositsPerBlock'] == curated_module_before['maxDepositsPerBlock'] + assert curated_module_after['minDepositBlockDistance'] == curated_module_before['minDepositBlockDistance'] + assert curated_module_after['priorityExitShareThreshold'] == curated_module_before['priorityExitShareThreshold'] + assert len(curated_module_after.items()) == len(curated_module_before.items()) + assert len(curated_module_after.items()) == 13 + + # Item 1.2 + sdvt_module_after = staking_router.getStakingModule(SDVT_MODULE_ID) + assert sdvt_module_after['stakeShareLimit'] == SDVT_MODULE_NEW_TARGET_SHARE_BP + assert sdvt_module_after['id'] == SDVT_MODULE_ID + assert sdvt_module_after['priorityExitShareThreshold'] == SDVT_MODULE_NEW_PRIORITY_EXIT_THRESHOLD_BP + assert sdvt_module_after['stakingModuleFee'] == SDVT_MODULE_MODULE_FEE_BP + assert sdvt_module_after['treasuryFee'] == SDVT_MODULE_TREASURY_FEE_BP + assert sdvt_module_after['maxDepositsPerBlock'] == SDVT_MODULE_MAX_DEPOSITS_PER_BLOCK + assert sdvt_module_after['minDepositBlockDistance'] == SDVT_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert sdvt_module_after['name'] == SDVT_MODULE_NAME + # additional checks to make sure no other fields were changed (if before state is available) + if sdvt_module_before is not None: + assert sdvt_module_after['id'] == sdvt_module_before['id'] + assert sdvt_module_after['stakingModuleAddress'] == sdvt_module_before['stakingModuleAddress'] + assert sdvt_module_after['stakingModuleFee'] == sdvt_module_before['stakingModuleFee'] + assert sdvt_module_after['treasuryFee'] == sdvt_module_before['treasuryFee'] + assert sdvt_module_after['status'] == sdvt_module_before['status'] + assert sdvt_module_after['name'] == sdvt_module_before['name'] + assert sdvt_module_after['lastDepositAt'] == sdvt_module_before['lastDepositAt'] + assert sdvt_module_after['lastDepositBlock'] == sdvt_module_before['lastDepositBlock'] + assert sdvt_module_after['exitedValidatorsCount'] == sdvt_module_before['exitedValidatorsCount'] + assert sdvt_module_after['maxDepositsPerBlock'] == sdvt_module_before['maxDepositsPerBlock'] + assert sdvt_module_after['minDepositBlockDistance'] == sdvt_module_before['minDepositBlockDistance'] + assert len(sdvt_module_after.items()) == len(sdvt_module_before.items()) + assert len(sdvt_module_after.items()) == 13 + + # Item 1.3 + a41_summary_after = staking_router.getNodeOperatorSummary(CURATED_MODULE_ID, A41_NO_ID) + assert a41_summary_after['targetLimitMode'] == NO_TARGET_LIMIT_MODE_AFTER + assert a41_summary_after['depositableValidatorsCount'] == 0 + assert a41_summary_after['targetValidatorsCount'] == NEW_A41_TARGET_LIMIT + assert curated_module.getNodeOperator(A41_NO_ID, True)['name'] == "A41" + # additional checks to make sure no other fields were changed (if before state is available) + if a41_summary_before is not None: + assert a41_summary_after['stuckValidatorsCount'] == a41_summary_before['stuckValidatorsCount'] + assert a41_summary_after['refundedValidatorsCount'] == a41_summary_before['refundedValidatorsCount'] + assert a41_summary_after['stuckPenaltyEndTimestamp'] == a41_summary_before['stuckPenaltyEndTimestamp'] + assert a41_summary_after['totalExitedValidators'] == a41_summary_before['totalExitedValidators'] + assert a41_summary_after['totalDepositedValidators'] == a41_summary_before['totalDepositedValidators'] + assert len(a41_summary_after.items()) == 8 + + # Items 1.4 + trp_limit_after, trp_period_duration_months_after = et_trp_registry.getLimitParameters() + trp_already_spent_amount_after, trp_spendable_balance_after, trp_period_start_after, trp_period_end_after = et_trp_registry.getPeriodState() + assert trp_limit_after == TRP_LIMIT_AFTER + assert trp_period_duration_months_after == TRP_PERIOD_DURATION_MONTHS + if TRP_ALREADY_SPENT_AFTER is not None: + assert trp_already_spent_amount_after == TRP_ALREADY_SPENT_AFTER + assert trp_spendable_balance_after == TRP_LIMIT_AFTER - TRP_ALREADY_SPENT_AFTER + assert trp_period_start_after == get_trp_period_start() + assert trp_period_end_after == get_trp_period_end() + + # scenraio test for TRP ET factory behavior after the vote + trp_limit_test(stranger) + + +def trp_limit_test(stranger): + + easy_track = interface.EasyTrack(EASY_TRACK) + ldo_token = interface.ERC20(LDO_TOKEN) + max_spend_at_once = 5_000_000 * 10**18 + trp_committee_account = accounts.at(TRP_COMMITTEE, force=True) + + chain.snapshot() + + # sleep to January so that TRP limit period does not change (it depends when tests are run) + chain.sleep(20 * 24 * 60 * 60) + chain.mine() + + _, spendableBalanceInPeriod, periodStartTimestamp, periodEndTimestamp = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY).getPeriodState({"from": AGENT}) + to_spend = interface.AllowedRecipientRegistry(ET_TRP_REGISTRY).getLimitParameters({"from": AGENT})[0] + if chain.time() >= periodStartTimestamp and chain.time() < periodEndTimestamp: + to_spend = spendableBalanceInPeriod + + # spend all in several transfers + recipients = [] + amounts = [] + while to_spend > 0: + recipients.append(trp_committee_account) + amounts.append(min(max_spend_at_once, to_spend)) + to_spend -= min(max_spend_at_once, to_spend) + + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + recipients, + amounts, + stranger, + ) + + # make sure there is nothing left so that you can't spend anymore + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + TRP_COMMITTEE, + TRP_TOP_UP_EVM_SCRIPT_FACTORY, + ldo_token, + [trp_committee_account], + [1], + stranger, + ) + + chain.revert() + +def et_limit_test(stranger, token, max_spend_at_once, to_spend, TRUSTED_CALLER, TOP_UP_ALLOWED_RECIPIENTS_FACTORY): + + easy_track = interface.EasyTrack(EASY_TRACK) + trusted_caller_account = accounts.at(TRUSTED_CALLER, force=True) + + chain.snapshot() + + # sleep to January so that ET limit period does not change (it depends when tests are run) + chain.sleep(20 * 24 * 60 * 60) + chain.mine() + + # spend all in several transfers + recipients = [] + amounts = [] + while to_spend > 0: + recipients.append(trusted_caller_account) + amounts.append(min(max_spend_at_once, to_spend)) + to_spend -= min(max_spend_at_once, to_spend) + + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + recipients, + amounts, + stranger, + ) + + # make sure there is nothing left so that you can't spend anymore + with reverts("SUM_EXCEEDS_SPENDABLE_BALANCE"): + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [1], + stranger, + ) + + chain.revert() + + +def finance_limit_test(stranger, token, to_spend, decimals, TRUSTED_CALLER, TOP_UP_ALLOWED_RECIPIENTS_FACTORY, ALLOWED_RECIPIENTS_REGISTRY): + + easy_track = interface.EasyTrack(EASY_TRACK) + trusted_caller_account = accounts.at(TRUSTED_CALLER, force=True) + + chain.snapshot() + + # for Finance limit check - we first raise ET limits to 10 x finance_limit to be able to spend via Finance + interface.AllowedRecipientRegistry(ALLOWED_RECIPIENTS_REGISTRY).setLimitParameters( + (to_spend / (10**decimals) * 10**18) * 10, # 10 x finance_limit + 3, # 3 months + {"from": AGENT} + ) + + # check that there is no way to spend more then expected + with reverts("APP_AUTH_FAILED"): + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [to_spend + 1], + stranger, + ) + + # spend the allowed balance + create_and_enact_payment_motion( + easy_track, + TRUSTED_CALLER, + TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + token, + [trusted_caller_account], + [to_spend], + stranger, + ) + + chain.revert() + + +def usds_wrap_happy_path(stranger): + USDC_FOR_TRANSFER = 1000 + USDS_TOKEN = "0xdC035D45d973E3EC169d2276DDab16f1e407384F" + + easy_track = interface.EasyTrack(EASY_TRACK) + usdc = interface.Usdc(USDC_TOKEN) + psmVariant1Actions = interface.PSMVariant1Actions(PSM_VARIANT1_ACTIONS) + usds_token = interface.Usds(USDS_TOKEN) + susds_token = interface.Susds(SUSDS_TOKEN) + + eoa = accounts[0] + + chain.snapshot() + + initial_susds_agent_balance = susds_token.balanceOf(AGENT) + + # fund EOA with USDC from Treasury + interface.AllowedRecipientRegistry(LIDO_LABS_ALLOWED_RECIPIENTS_REGISTRY).addRecipient( + eoa.address, + "EOA_test", + {"from": AGENT} + ) + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + usdc, + [eoa], + [USDC_FOR_TRANSFER * 10**6], + stranger, + ) + assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 + assert usds_token.balanceOf(eoa.address) == 0 + assert susds_token.balanceOf(eoa.address) == 0 + + # wrap USDC to sUSDS via PSM + usdc.approve(PSM_VARIANT1_ACTIONS, USDC_FOR_TRANSFER * 10**6, {"from": eoa}) + psmVariant1Actions.swapAndDeposit(eoa.address, USDC_FOR_TRANSFER * 10**6, USDC_FOR_TRANSFER * 10**18, {"from": eoa}) + assert usdc.balanceOf(eoa.address) == 0 + assert usds_token.balanceOf(eoa.address) == 0 + susds_balance = susds_token.balanceOf(eoa.address) + assert susds_balance <= USDC_FOR_TRANSFER * 10**18 + assert susds_balance >= USDC_FOR_TRANSFER * 10**18 * 0.9 + + # send sUSDS back to Treasury + susds_token.transfer(AGENT, susds_balance, {"from": eoa}) + assert susds_token.balanceOf(eoa.address) == 0 + assert susds_token.balanceOf(AGENT) == susds_balance + initial_susds_agent_balance + print("swapped", USDC_FOR_TRANSFER, "USDC to", susds_balance / 10**18, "sUSDS") + + # send sUSDS again to EOA via Easy Track payment from Treasury + create_and_enact_payment_motion( + easy_track, + LIDO_LABS_TRUSTED_CALLER, + LIDO_LABS_TOP_UP_ALLOWED_RECIPIENTS_FACTORY, + susds_token, + [eoa], + [susds_balance], + stranger, + ) + assert susds_token.balanceOf(eoa.address) == susds_balance + assert susds_token.balanceOf(AGENT) == initial_susds_agent_balance + + # wait 1 year to accumulate interest on sUSDS + chain.sleep(365 * 24 * 3600) + chain.mine() + susds_token.drip({"from": eoa}) + INTEREST_RATE = 0.04 + + # unwrap sUSDS to USDC + susds_token.approve(PSM_VARIANT1_ACTIONS, susds_balance, {"from": eoa}) + psmVariant1Actions.withdrawAndSwap(eoa.address, USDC_FOR_TRANSFER * 10**6 * (1 + INTEREST_RATE), USDC_FOR_TRANSFER * 10**18 * (1 + INTEREST_RATE), {"from": eoa}) + usdc_balance = usdc.balanceOf(eoa.address) + print("swapped", susds_balance / 10**18, "sUSDS to", usdc_balance / 10**6, "USDC, leftover:", susds_token.balanceOf(eoa.address) / 10**18, "sUSDS") + assert susds_token.balanceOf(eoa.address) < 5.0 * 10**18 # leftover from interest surplus + assert usdc.balanceOf(eoa.address) == USDC_FOR_TRANSFER * 10**6 * (1 + INTEREST_RATE) + + chain.revert() + + +def prepare_agent_for_dai_payment(amount: int): + agent, dai = interface.Agent(AGENT), interface.Dai(DAI_TOKEN) + if dai.balanceOf(agent) < amount: + dai_ward_impersonated = accounts.at("0x9759A6Ac90977b93B58547b4A71c78317f391A28", force=True) + dai.mint(agent, amount, {"from": dai_ward_impersonated}) + + assert dai.balanceOf(agent) >= amount, f"Insufficient DAI balance" + + +def prepare_agent_for_usdc_payment(amount: int): + agent, usdc = interface.Agent(AGENT), interface.Usdc(USDC_TOKEN) + if usdc.balanceOf(agent) < amount: + usdc_minter = accounts.at("0x5B6122C109B78C6755486966148C1D70a50A47D7", force=True) + usdc_controller = accounts.at("0x79E0946e1C186E745f1352d7C21AB04700C99F71", force=True) + usdc_master_minter = interface.UsdcMasterMinter("0xE982615d461DD5cD06575BbeA87624fda4e3de17") + usdc_master_minter.incrementMinterAllowance(amount, {"from": usdc_controller}) + usdc.mint(agent, amount, {"from": usdc_minter}) + + assert usdc.balanceOf(agent) >= amount, "Insufficient USDC balance" + + +def prepare_agent_for_usdt_payment(amount: int): + agent, usdt = interface.Agent(AGENT), interface.Usdt(USDT_TOKEN) + if usdt.balanceOf(agent) < amount: + usdt_owner = accounts.at("0xC6CDE7C39eB2f0F0095F41570af89eFC2C1Ea828", force=True) + usdt.issue(amount, {"from": usdt_owner}) + usdt.transfer(agent, amount, {"from": usdt_owner}) + + assert usdt.balanceOf(agent) >= amount, "Insufficient USDT balance" + + +def prepare_agent_for_susds_payment(amount: int): + agent, susds = interface.Agent(AGENT), interface.ERC20(SUSDS_TOKEN) + if susds.balanceOf(agent) < amount: + susds_whale = accounts.at("0xBc65ad17c5C0a2A4D159fa5a503f4992c7B545FE", force=True) + susds.transfer(agent, amount, {"from": susds_whale}) + + assert susds.balanceOf(agent) >= amount, "Insufficient sUSDS balance" + + +def prepare_agent_for_ldo_payment(amount: int): + agent, ldo = interface.Agent(AGENT), interface.ERC20(LDO_TOKEN) + assert ldo.balanceOf(agent) >= amount, "Insufficient LDO balance 🫡" + + +def prepare_agent_for_steth_payment(amount: int): + STETH_TRANSFER_MAX_DELTA = 2 + + agent, steth = interface.Agent(AGENT), interface.Lido(STETH_TOKEN) + eth_whale = accounts.at("0x00000000219ab540356cBB839Cbe05303d7705Fa", force=True) + if steth.balanceOf(agent) < amount: + steth.submit(ZERO_ADDRESS, {"from": eth_whale, "value": amount + 2 * STETH_TRANSFER_MAX_DELTA}) + steth.transfer(agent, amount + STETH_TRANSFER_MAX_DELTA, {"from": eth_whale}) + assert steth.balanceOf(agent) >= amount, "Insufficient stETH balance" diff --git a/utils/balance.py b/utils/balance.py index 2ed745c26..dbcb160ec 100644 --- a/utils/balance.py +++ b/utils/balance.py @@ -3,7 +3,10 @@ def set_balance_in_wei(address, balance): - account = accounts.at(address, force=True) + # Accept both a plain address string and a Brownie Account object + address_str = address.address if hasattr(address, "address") else address + + account = accounts.at(address_str, force=True) providers = ["evm_setAccountBalance", "hardhat_setBalance", "anvil_setBalance"] for provider in providers: @@ -11,12 +14,12 @@ def set_balance_in_wei(address, balance): break try: - web3.provider.make_request(provider, [address, hex(balance)]) + web3.provider.make_request(provider, [address_str, hex(balance)]) except ValueError as e: if e.args[0].get("message") != f"Method {provider} is not supported": raise e - assert account.balance() == balance, f"Failed to set balance {balance} for account: {address}" + assert account.balance() == balance, f"Failed to set balance {balance} for account: {address_str}" return account diff --git a/utils/config.py b/utils/config.py index 573bd42b2..23b581247 100644 --- a/utils/config.py +++ b/utils/config.py @@ -262,10 +262,6 @@ def sandbox(self) -> interface.SimpleDVT: def legacy_oracle(self) -> interface.LegacyOracle: return interface.LegacyOracle(LEGACY_ORACLE) - @property - def token_rate_notifier(self) -> interface.TokenRateNotifier: - return interface.LegacyOracle(L1_TOKEN_RATE_NOTIFIER) - @property def deposit_security_module_v1(self) -> interface.DepositSecurityModule: return interface.DepositSecurityModuleV1(DEPOSIT_SECURITY_MODULE_V1) @@ -314,6 +310,30 @@ def triggerable_withdrawals_gateway(self): def withdrawal_queue(self) -> interface.WithdrawalQueueERC721: return interface.WithdrawalQueueERC721(WITHDRAWAL_QUEUE) + @property + def vault_hub(self) -> interface.VaultHub: + return interface.VaultHub(VAULT_HUB) + + @property + def accounting(self) -> interface.Accounting: + return interface.Accounting(ACCOUNTING) + + @property + def operator_grid(self) -> interface.OperatorGrid: + return interface.OperatorGrid(OPERATOR_GRID) + + @property + def lazy_oracle(self) -> interface.LazyOracle: + return interface.LazyOracle(LAZY_ORACLE) + + @property + def predeposit_guarantee(self) -> interface.PredepositGuarantee: + return interface.PredepositGuarantee(PREDEPOSIT_GUARANTEE) + + @property + def staking_vault_beacon(self) -> interface.UpgradeableBeacon: + return interface.UpgradeableBeacon(STAKING_VAULT_BEACON) + @property def lido_locator(self) -> interface.LidoLocator: return interface.LidoLocator(LIDO_LOCATOR) diff --git a/utils/dual_governance.py b/utils/dual_governance.py index 69beabce2..91307e874 100644 --- a/utils/dual_governance.py +++ b/utils/dual_governance.py @@ -59,7 +59,8 @@ def process_proposals(proposal_ids: Sequence[int]): submitted_proposals = [] scheduled_proposals = [] - for proposal_id in proposals_to_be_processed: + copy_proposals_to_be_processed = proposals_to_be_processed.copy() + for proposal_id in copy_proposals_to_be_processed: (_, _, _, _, proposal_status) = contracts.emergency_protected_timelock.getProposalDetails(proposal_id) if proposal_status == PROPOSAL_STATUS["submitted"]: submitted_proposals.append(proposal_id) @@ -87,7 +88,7 @@ def process_proposals(proposal_ids: Sequence[int]): if len(scheduled_proposals): chain.sleep(after_schedule_delay + 1) - wait_for_noon_utc_to_satisfy_time_constrains() + wait_for_target_time_to_satisfy_time_constrains() for proposal_id in scheduled_proposals: contracts.emergency_protected_timelock.execute(proposal_id, {"from": stranger}) @@ -174,20 +175,20 @@ def wait_for_time_window(from_hour_utc: int, to_hour_utc: int): chain.sleep(sleep_time) -def wait_for_noon_utc_to_satisfy_time_constrains(): +def wait_for_target_time_to_satisfy_time_constrains(): current_time = chain.time() - noon_offset = 12 * 60 * 60 - seconds_per_day = noon_offset * 2 + target_time = 16 * 60 * 60 # 16:00 UTC + seconds_per_day = 24 * 60 * 60 day_start = current_time - (current_time % seconds_per_day) - today_noon = day_start + noon_offset + today_target_time = day_start + target_time - if current_time >= today_noon: - target_noon = today_noon + seconds_per_day + if current_time >= today_target_time: + target_time = today_target_time + seconds_per_day else: - target_noon = today_noon + target_time = today_target_time - chain.sleep(target_noon - current_time) + chain.sleep(target_time - current_time) def is_proposal_executed(proposal_id: int) -> bool: diff --git a/utils/finance.py b/utils/finance.py index 5d0cae834..d5f4acc94 100644 --- a/utils/finance.py +++ b/utils/finance.py @@ -6,6 +6,7 @@ LIDO, WETH_TOKEN, DAI_TOKEN, + MATIC_TOKEN, ) @@ -74,6 +75,19 @@ def make_dai_payout(*not_specified, target_address: str, dai_in_wei: int, refere finance=contracts.finance, ) +def make_matic_payout(*not_specified, target_address: str, matic_in_wei: int, reference: str) -> Tuple[str, str]: + """Encode MATIC payout.""" + if not_specified: + raise ValueError("Please, specify all arguments with keywords.") + + return _encode_token_transfer( + token_address=MATIC_TOKEN, + recipient=target_address, + amount=matic_in_wei, + reference=reference, + finance=contracts.finance, + ) + def _encode_token_transfer(token_address, recipient, amount, reference, finance): return (finance.address, finance.newImmediatePayment.encode_input(token_address, recipient, amount, reference)) diff --git a/utils/import_current_votes.py b/utils/import_current_votes.py index 73005f71d..71466fca4 100644 --- a/utils/import_current_votes.py +++ b/utils/import_current_votes.py @@ -42,7 +42,7 @@ def is_there_any_upgrade_scripts() -> bool: return len(get_upgrade_script_files()) > 0 -def start_and_execute_votes(dao_voting, helpers) -> tuple[List[str], List[TransactionReceipt]]: +def start_and_execute_votes(dao_voting, helpers, vote_file_index = None) -> tuple[List[str], List[TransactionReceipt]]: vote_files = get_vote_script_files() upgrade_files = get_upgrade_script_files() vote_files.extend(upgrade_files) @@ -50,7 +50,9 @@ def start_and_execute_votes(dao_voting, helpers) -> tuple[List[str], List[Transa vote_ids = [] vote_transactions = [] - for vote_file in sorted(vote_files): + for current_vote_file_index, vote_file in enumerate(sorted(vote_files)): + if vote_file_index is not None and current_vote_file_index != vote_file_index: + continue script_name = os.path.splitext(os.path.basename(vote_file))[0] print(f"Starting voting from script '{script_name}'...") name_for_import = "scripts." + script_name diff --git a/utils/repo.py b/utils/repo.py index c44f6ec16..4d0d80403 100644 --- a/utils/repo.py +++ b/utils/repo.py @@ -25,10 +25,6 @@ def add_implementation_to_voting_app_repo(version, address, content_uri): return _add_implementation_to_repo(contracts.voting_app_repo, version, address, content_uri) -def add_implementation_to_oracle_app_repo(version, address, content_uri): - return _add_implementation_to_repo(contracts.oracle_app_repo, version, address, content_uri) - - def create_new_app_repo(name, manager, version, address, content_uri): apm_registry = contracts.apm_registry diff --git a/utils/test/easy_track_helpers.py b/utils/test/easy_track_helpers.py index 1a8bb7aea..96d154f3c 100644 --- a/utils/test/easy_track_helpers.py +++ b/utils/test/easy_track_helpers.py @@ -77,9 +77,15 @@ def create_and_enact_payment_motion( create_and_enact_motion(easy_track, trusted_caller, factory, calldata, stranger) recievers_balance_after = [balance_of(reciever, token) for reciever in recievers] + recievers_total_amounts = {} for i in range(len(recievers)): + reciever_address = recievers[i].address + recievers_total_amounts[reciever_address] = recievers_total_amounts.get(reciever_address, 0) + transfer_amounts[i] + + for i in range(len(recievers)): + reciever_address = recievers[i].address assert almostEqWithDiff( - recievers_balance_after[i], recievers_balance_before[i] + transfer_amounts[i], STETH_ERROR_MARGIN_WEI + recievers_balance_after[i], recievers_balance_before[i] + recievers_total_amounts[reciever_address], STETH_ERROR_MARGIN_WEI ) agent_balance_after = balance_of(agent, token) @@ -140,26 +146,25 @@ def check_add_and_remove_recipient_with_voting(registry, helpers, ldo_holder, da assert not registry.isRecipientAllowed(recipient_candidate) - call_script_items = [ - agent_forward( - [ - ( - registry.address, - registry.addRecipient.encode_input(recipient_candidate, title), - ) - ] + vote_input = [ + ( + registry.address, + registry.addRecipient.encode_input(recipient_candidate, title), ) ] + + call_script_items = submit_proposals([([agent_forward(vote_input)], "")]) vote_desc_items = ["Add recipient"] vote_items = bake_vote_items(vote_desc_items, call_script_items) vote_id = create_vote(vote_items, {"from": ldo_holder})[0] - helpers.execute_vote( + vote_tx = helpers.execute_vote( vote_id=vote_id, accounts=accounts, dao_voting=dao_voting, ) + process_proposals([vote_tx.events["ProposalSubmitted"][1]["proposalId"]]) assert registry.isRecipientAllowed(recipient_candidate) assert len(registry.getAllowedRecipients()) == recipients_length_before + 1, "Wrong whitelist length" diff --git a/utils/test/event_validators/allowed_recipients_registry.py b/utils/test/event_validators/allowed_recipients_registry.py index f418c38a3..bd740281e 100644 --- a/utils/test/event_validators/allowed_recipients_registry.py +++ b/utils/test/event_validators/allowed_recipients_registry.py @@ -12,6 +12,7 @@ def validate_set_limit_parameter_event( "CurrentPeriodAdvanced", "LimitsParametersChanged", "ScriptResult", + "Executed", ] validate_events_chain([e.name for e in event], _events_chain) @@ -22,11 +23,11 @@ def validate_set_limit_parameter_event( assert event.count("LimitsParametersChanged") == 1 assert event["LimitsParametersChanged"]["_limit"] == limit assert event["LimitsParametersChanged"]["_periodDurationMonths"] == period_duration_month - if emitted_by is not None: - event_emitted_by = convert.to_address(event["LimitsParametersChanged"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["LimitsParametersChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_update_spent_amount_event( @@ -60,10 +61,16 @@ def validate_update_spent_amount_event( def validate_set_spent_amount_event( event: EventDict, new_spent_amount: int, + emitted_by: str | None = None, ): - _events_chain = ["LogScriptCall", "LogScriptCall", "SpentAmountChanged", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "SpentAmountChanged", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) assert event.count("SpentAmountChanged") == 1 assert event["SpentAmountChanged"]["_newSpentAmount"] == new_spent_amount + + event_emitted_by = convert.to_address(event["SpentAmountChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/allowed_tokens_registry.py b/utils/test/event_validators/allowed_tokens_registry.py index 1430ad972..55750b3bb 100644 --- a/utils/test/event_validators/allowed_tokens_registry.py +++ b/utils/test/event_validators/allowed_tokens_registry.py @@ -19,8 +19,7 @@ def validate_add_token_event( token ), f"Wrong token address {event['TokenAdded']['_token']} but expected {token}" - if emitted_by is not None: - event_emitted_by = convert.to_address(event["TokenAdded"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + event_emitted_by = convert.to_address(event["TokenAdded"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/aragon.py b/utils/test/event_validators/aragon.py index 475cfcb08..54d09c53c 100644 --- a/utils/test/event_validators/aragon.py +++ b/utils/test/event_validators/aragon.py @@ -1,24 +1,100 @@ from typing import Tuple, Annotated from .common import validate_events_chain from brownie.network.event import EventDict +from brownie import convert -def validate_app_update_event(event: EventDict, app_id: str, app_address: str): - _ldo_events_chain = ["LogScriptCall", "SetApp"] +def validate_push_to_repo_event(event: EventDict, semantic_version: Annotated[Tuple[int, int, int], 3]): + _ldo_events_chain = ["LogScriptCall", "NewVersion"] validate_events_chain([e.name for e in event], _ldo_events_chain) - assert event.count("SetApp") == 1 + assert event.count("NewVersion") == 1 - assert event["SetApp"]["appId"] == app_id, "Wrong app id" - assert event["SetApp"]["app"] == app_address, "Wrong app address" + assert event["NewVersion"]["semanticVersion"] == semantic_version, "Wrong version" +def validate_aragon_grant_permission_event( + event, + entity: str, + app: str, + role: str, + emitted_by: str, +) -> None: + """ + Validate Aragon ACL SetPermission event for granting permission via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetPermission", "ScriptResult", "Executed"] -def validate_push_to_repo_event(event: EventDict, semantic_version: Annotated[Tuple[int, int, int], 3]): - _ldo_events_chain = ["LogScriptCall", "NewVersion"] + validate_events_chain([e.name for e in event], _events_chain) - validate_events_chain([e.name for e in event], _ldo_events_chain) + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetPermission") == 1, f"Expected 1 SetPermission, got {event.count('SetPermission')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" - assert event.count("NewVersion") == 1 + assert event["SetPermission"]["allowed"] is True, "Permission should be granted (allowed=True)" + assert event["SetPermission"]["entity"] == entity, f"Wrong entity: expected {entity}, got {event['SetPermission']['entity']}" + assert event["SetPermission"]["app"] == app, f"Wrong app: expected {app}, got {event['SetPermission']['app']}" + assert event["SetPermission"]["role"] == role, f"Wrong role: expected {role}, got {event['SetPermission']['role']}" - assert event["NewVersion"]["semanticVersion"] == semantic_version, "Wrong version" + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" + + +def validate_aragon_revoke_permission_event( + event, + entity: str, + app: str, + role: str, + emitted_by: str, +) -> None: + """ + Validate Aragon ACL SetPermission event for revoking permission via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetPermission", "ScriptResult", "Executed"] + + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetPermission") == 1, f"Expected 1 SetPermission, got {event.count('SetPermission')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" + + assert event["SetPermission"]["allowed"] is False, "Permission should be revoked (allowed=False)" + assert event["SetPermission"]["entity"] == entity, f"Wrong entity: expected {entity}, got {event['SetPermission']['entity']}" + assert event["SetPermission"]["app"] == app, f"Wrong app: expected {app}, got {event['SetPermission']['app']}" + assert event["SetPermission"]["role"] == role, f"Wrong role: expected {role}, got {event['SetPermission']['role']}" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" + + +def validate_aragon_set_app_event( + event, + app_id: str, + app: str, + emitted_by: str, +) -> None: + """ + Validate Aragon Kernel SetApp event via DG proposal. + Ensures only expected events are fired and all parameters are correct. + """ + _events_chain = ["LogScriptCall", "SetApp", "ScriptResult", "Executed"] + + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1, f"Expected 1 LogScriptCall, got {event.count('LogScriptCall')}" + assert event.count("SetApp") == 1, f"Expected 1 SetApp, got {event.count('SetApp')}" + assert event.count("ScriptResult") == 1, f"Expected 1 ScriptResult, got {event.count('ScriptResult')}" + assert event.count("Executed") == 1, f"Expected 1 Executed, got {event.count('Executed')}" + + assert event["SetApp"]["appId"] == app_id, f"Wrong appId: expected {app_id}, got {event['SetApp']['appId']}" + assert event["SetApp"]["app"] == app, f"Wrong app: expected {app}, got {event['SetApp']['app']}" + + assert convert.to_address(event["SetApp"]["_emitted_by"]) == convert.to_address( + emitted_by + ), f"Wrong event emitter: expected {emitted_by}" diff --git a/utils/test/event_validators/csm.py b/utils/test/event_validators/csm.py index 38549b311..975fecec9 100644 --- a/utils/test/event_validators/csm.py +++ b/utils/test/event_validators/csm.py @@ -13,19 +13,14 @@ def validate_set_key_removal_charge_event( event: EventDict, key_removal_charge: int, emitted_by: str | None = None, - is_dg_event: bool = False, ): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "KeyRemovalChargeSet", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "KeyRemovalChargeSet", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "KeyRemovalChargeSet", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) assert event.count("KeyRemovalChargeSet") == 1 assert event["KeyRemovalChargeSet"]["amount"] == key_removal_charge - if emitted_by is not None: - event_emitted_by = convert.to_address(event["KeyRemovalChargeSet"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + event_emitted_by = convert.to_address(event["KeyRemovalChargeSet"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/dual_governance.py b/utils/test/event_validators/dual_governance.py index 661354e3d..922fb93c5 100644 --- a/utils/test/event_validators/dual_governance.py +++ b/utils/test/event_validators/dual_governance.py @@ -1,6 +1,7 @@ from brownie.network.event import EventDict from brownie import convert, web3 from .common import validate_events_chain +from utils.config import DUAL_GOVERNANCE, TIMELOCK def validate_dual_governance_submit_event( @@ -10,7 +11,6 @@ def validate_dual_governance_submit_event( executor: str, metadata: str = None, proposal_calls: any = None, - emitted_by: list[str] = None, ) -> None: _events_chain = ["LogScriptCall", "ProposalSubmitted", "ProposalSubmitted"] @@ -21,12 +21,11 @@ def validate_dual_governance_submit_event( assert event["ProposalSubmitted"][0]["id"] == proposal_id, "Wrong proposalId" assert event["ProposalSubmitted"][0]["executor"] == executor, "Wrong executor" - if proposal_calls: - assert len(event["ProposalSubmitted"][0]["calls"]) == len(proposal_calls), "Wrong callsCount" - for i in range(0, len(proposal_calls)): - assert event["ProposalSubmitted"][0]["calls"][i][0] == proposal_calls[i]["target"], f"Wrong target {i}: {event['ProposalSubmitted'][0]['calls'][i][0]} : {proposal_calls[i]['target']}" - assert event["ProposalSubmitted"][0]["calls"][i][1] == proposal_calls[i]["value"], f"Wrong value {i}" - assert event["ProposalSubmitted"][0]["calls"][i][2] == proposal_calls[i]["data"], f'Wrong data {i}' + assert len(event["ProposalSubmitted"][0]["calls"]) == len(proposal_calls), "Wrong callsCount" + for i in range(0, len(proposal_calls)): + assert event["ProposalSubmitted"][0]["calls"][i][0] == proposal_calls[i]["target"], f"Wrong target {i}: {event['ProposalSubmitted'][0]['calls'][i][0]} : {proposal_calls[i]['target']}" + assert event["ProposalSubmitted"][0]["calls"][i][1] == proposal_calls[i]["value"], f"Wrong value {i}" + assert event["ProposalSubmitted"][0]["calls"][i][2] == proposal_calls[i]["data"], f'Wrong data {i}' assert event["ProposalSubmitted"][1]["proposalId"] == proposal_id, "Wrong proposalId" assert event["ProposalSubmitted"][1]["proposerAccount"] == proposer, "Wrong proposer" @@ -34,12 +33,13 @@ def validate_dual_governance_submit_event( if metadata: assert event["ProposalSubmitted"][1]["metadata"] == metadata, f"Wrong metadata {event['ProposalSubmitted'][1]['metadata']}" - if emitted_by is not None: - assert len(event["ProposalSubmitted"]) == len(emitted_by), "Wrong emitted_by count" - for i in range(0, len(emitted_by)): - assert convert.to_address(event["ProposalSubmitted"][i]["_emitted_by"]) == convert.to_address( - emitted_by[i] - ), "Wrong event emitter" + assert len(event["ProposalSubmitted"]) == 2, "Wrong emitted_by count" + assert convert.to_address(event["ProposalSubmitted"][0]["_emitted_by"]) == convert.to_address( + TIMELOCK + ), "Wrong event emitter" + assert convert.to_address(event["ProposalSubmitted"][1]["_emitted_by"]) == convert.to_address( + DUAL_GOVERNANCE + ), "Wrong event emitter" def validate_dual_governance_tiebreaker_activation_timeout_set_event( diff --git a/utils/test/event_validators/easy_track.py b/utils/test/event_validators/easy_track.py index 2216ec890..a6952c21f 100644 --- a/utils/test/event_validators/easy_track.py +++ b/utils/test/event_validators/easy_track.py @@ -13,20 +13,20 @@ class EVMScriptFactoryAdded(NamedTuple): def validate_evmscript_factory_added_event( event: EventDict, p: EVMScriptFactoryAdded, - _events_chain=["LogScriptCall", "EVMScriptFactoryAdded"], - emitted_by: str | None = None, + emitted_by: str, ): + _events_chain=["LogScriptCall", "EVMScriptFactoryAdded"] validate_events_chain([e.name for e in event], _events_chain) assert event.count("EVMScriptFactoryAdded") == 1 assert event["EVMScriptFactoryAdded"]["_evmScriptFactory"] == p.factory_addr assert event["EVMScriptFactoryAdded"]["_permissions"] == p.permissions - if emitted_by is not None: - event_emitted_by = convert.to_address(event["EVMScriptFactoryAdded"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["EVMScriptFactoryAdded"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_evmscript_factory_removed_event(event: EventDict, factory_addr: str, emitted_by: str | None = None): @@ -37,11 +37,11 @@ def validate_evmscript_factory_removed_event(event: EventDict, factory_addr: str assert event.count("EVMScriptFactoryRemoved") == 1 assert event["EVMScriptFactoryRemoved"]["_evmScriptFactory"] == factory_addr - if emitted_by is not None: - event_emitted_by = convert.to_address(event["EVMScriptFactoryRemoved"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["EVMScriptFactoryRemoved"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_motions_count_limit_changed_event( @@ -54,8 +54,8 @@ def validate_motions_count_limit_changed_event( assert event.count("MotionsCountLimitChanged") == 1 assert event["MotionsCountLimitChanged"]["_newMotionsCountLimit"] == motions_count_limit - if emitted_by is not None: - event_emitted_by = convert.to_address(event["MotionsCountLimitChanged"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["MotionsCountLimitChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/hash_consensus.py b/utils/test/event_validators/hash_consensus.py index 93c897f2d..62dcfb157 100644 --- a/utils/test/event_validators/hash_consensus.py +++ b/utils/test/event_validators/hash_consensus.py @@ -3,12 +3,9 @@ from brownie import convert -def validate_hash_consensus_member_removed(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None, is_dg_event: bool = False): +def validate_hash_consensus_member_removed(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "MemberRemoved", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "MemberRemoved", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "MemberRemoved", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) @@ -17,17 +14,14 @@ def validate_hash_consensus_member_removed(event: EventDict, member: str, new_qu assert event["MemberRemoved"]["addr"] == member assert event["MemberRemoved"]["newQuorum"] == new_quorum assert event["MemberRemoved"]["newTotalMembers"] == new_total_members - if emitted_by is not None: - event_emitted_by = convert.to_address(event["MemberRemoved"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" - -def validate_hash_consensus_member_added(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None, is_dg_event: bool = False): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "MemberAdded", "ScriptResult", "Executed"] - else: - _events_chain =["LogScriptCall", "LogScriptCall", "MemberAdded", "ScriptResult"] + + event_emitted_by = convert.to_address(event["MemberRemoved"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + +def validate_hash_consensus_member_added(event: EventDict, member: str, new_quorum: int, new_total_members: int, emitted_by: str = None): + _events_chain = ["LogScriptCall", "LogScriptCall", "MemberAdded", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) @@ -36,8 +30,8 @@ def validate_hash_consensus_member_added(event: EventDict, member: str, new_quor assert event["MemberAdded"]["addr"] == member assert event["MemberAdded"]["newQuorum"] == new_quorum assert event["MemberAdded"]["newTotalMembers"] == new_total_members - if emitted_by is not None: - event_emitted_by = convert.to_address(event["MemberAdded"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["MemberAdded"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/node_operators_registry.py b/utils/test/event_validators/node_operators_registry.py index a8a81896c..7f76faa38 100644 --- a/utils/test/event_validators/node_operators_registry.py +++ b/utils/test/event_validators/node_operators_registry.py @@ -27,6 +27,7 @@ class NodeOperatorRewardAddressSetItem(NamedTuple): class TargetValidatorsCountChanged(NamedTuple): nodeOperatorId: int targetValidatorsCount: int + targetLimitMode: int def validate_node_operator_added_event( event: EventDict, node_operator_item: NodeOperatorItem @@ -60,12 +61,9 @@ def validate_node_operator_staking_limit_set_event( def validate_node_operator_name_set_event( - event: EventDict, node_operator_name_item: NodeOperatorNameSetItem, emitted_by: str = None, is_dg_event=False + event: EventDict, node_operator_name_item: NodeOperatorNameSetItem, emitted_by: str = None ): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorNameSet", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorNameSet", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorNameSet", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) @@ -74,18 +72,14 @@ def validate_node_operator_name_set_event( assert event["NodeOperatorNameSet"]["nodeOperatorId"] == node_operator_name_item.nodeOperatorId assert event["NodeOperatorNameSet"]["name"] == node_operator_name_item.name - if emitted_by is not None: - assert convert.to_address(event["NodeOperatorNameSet"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["NodeOperatorNameSet"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_node_operator_reward_address_set_event( - event: EventDict, node_operator_reward_address_item: NodeOperatorRewardAddressSetItem, emitted_by: str = None, is_dg_event=False + event: EventDict, node_operator_reward_address_item: NodeOperatorRewardAddressSetItem, emitted_by: str = None ): - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorRewardAddressSet", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorRewardAddressSet", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "NodeOperatorRewardAddressSet", "ScriptResult", "Executed"] validate_events_chain([e.name for e in event], _events_chain) @@ -94,20 +88,30 @@ def validate_node_operator_reward_address_set_event( assert event["NodeOperatorRewardAddressSet"]["nodeOperatorId"] == node_operator_reward_address_item.nodeOperatorId assert event["NodeOperatorRewardAddressSet"]["rewardAddress"] == node_operator_reward_address_item.reward_address - if emitted_by is not None: - assert convert.to_address(event["NodeOperatorRewardAddressSet"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["NodeOperatorRewardAddressSet"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" -def validate_target_validators_count_changed_event(event: EventDict, t: TargetValidatorsCountChanged): - _events_chain = ["LogScriptCall", "LogScriptCall", "TargetValidatorsCountChanged", "KeysOpIndexSet", "NonceChanged", "ScriptResult"] +def validate_target_validators_count_changed_event(event: EventDict, t: TargetValidatorsCountChanged, emitted_by: str): + _events_chain = ['LogScriptCall', 'TargetValidatorsCountChanged', 'KeysOpIndexSet', 'NonceChanged', 'ScriptResult', 'Executed'] validate_events_chain([e.name for e in event], _events_chain) assert event.count("TargetValidatorsCountChanged") == 1 - assert event["TargetValidatorsCountChanged"]["nodeOperatorId"] == t.nodeOperatorId - assert event["TargetValidatorsCountChanged"]["targetValidatorsCount"] == t.targetValidatorsCount + #assert event["TargetValidatorsCountChanged"]["nodeOperatorId"] == t.nodeOperatorId + #assert event["TargetValidatorsCountChanged"]["targetValidatorsCount"] == t.targetValidatorsCount + + # validate arguments based on their positional order + # NodeOperatorsRegistry + # event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode) + assert event["TargetValidatorsCountChanged"]["ARG0_VALUE"] == t.nodeOperatorId + assert event["TargetValidatorsCountChanged"]["ARG1_VALUE"] == t.targetValidatorsCount + assert event["TargetValidatorsCountChanged"]["ARG2_VALUE"] == t.targetLimitMode + + assert convert.to_address(event["TargetValidatorsCountChanged"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_node_operator_deactivated(event: EventDict, node_operator_id: int): _events_chain = [ diff --git a/utils/test/event_validators/oracle_report_sanity_checker.py b/utils/test/event_validators/oracle_report_sanity_checker.py index 2989bc407..75aa4875a 100644 --- a/utils/test/event_validators/oracle_report_sanity_checker.py +++ b/utils/test/event_validators/oracle_report_sanity_checker.py @@ -15,11 +15,11 @@ def validate_exited_validators_per_day_limit_event(event: EventDict, value: int, assert event.count("ExitedValidatorsPerDayLimitSet") == 1 assert event["ExitedValidatorsPerDayLimitSet"]["exitedValidatorsPerDayLimit"] == value - if emitted_by is not None: - event_emitted_by = convert.to_address(event["ExitedValidatorsPerDayLimitSet"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["ExitedValidatorsPerDayLimitSet"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_appeared_validators_limit_event(event: EventDict, value: int, emitted_by: str | None = None): _events_chain = [ @@ -34,11 +34,11 @@ def validate_appeared_validators_limit_event(event: EventDict, value: int, emitt assert event.count("AppearedValidatorsPerDayLimitSet") == 1 assert event["AppearedValidatorsPerDayLimitSet"]["appearedValidatorsPerDayLimit"] == value - if emitted_by is not None: - event_emitted_by = convert.to_address(event["AppearedValidatorsPerDayLimitSet"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["AppearedValidatorsPerDayLimitSet"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" def validate_initial_slashing_and_penalties_event(event: EventDict, value: int, emitted_by: str | None = None): _events_chain = [ @@ -53,8 +53,8 @@ def validate_initial_slashing_and_penalties_event(event: EventDict, value: int, assert event.count("InitialSlashingAmountSet") == 1 assert event["InitialSlashingAmountSet"]["initialSlashingAmountPWei"] == value - if emitted_by is not None: - event_emitted_by = convert.to_address(event["InitialSlashingAmountSet"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" \ No newline at end of file + + event_emitted_by = convert.to_address(event["InitialSlashingAmountSet"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" \ No newline at end of file diff --git a/utils/test/event_validators/payout.py b/utils/test/event_validators/payout.py index 1aa084cf0..07e8c5ad5 100644 --- a/utils/test/event_validators/payout.py +++ b/utils/test/event_validators/payout.py @@ -5,6 +5,7 @@ from brownie.network.event import EventDict from .common import validate_events_chain from utils.finance import ZERO_ADDRESS +from brownie import convert class Payout(NamedTuple): @@ -14,8 +15,8 @@ class Payout(NamedTuple): amount: int -def validate_token_payout_event(event: EventDict, p: Payout, is_steth: bool = False): - _token_events_chain = ["LogScriptCall", "NewPeriod", "NewTransaction", "Transfer"] +def validate_token_payout_event(event: EventDict, p: Payout, is_steth: bool = False, emitted_by: str = None): + _token_events_chain = ["LogScriptCall", "NewPeriod", "NewPeriod", "NewTransaction", "Transfer"] if is_steth: _token_events_chain += ["TransferShares"] @@ -49,6 +50,10 @@ def validate_token_payout_event(event: EventDict, p: Payout, is_steth: bool = Fa assert event["NewTransaction"]["entity"] == p.to_addr assert event["NewTransaction"]["amount"] == p.amount + assert convert.to_address(event["VaultTransfer"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" + def validate_ether_payout_event(event: EventDict, p: Payout): _ether_events_chain = ["LogScriptCall", "NewPeriod", "NewTransaction", "VaultTransfer"] diff --git a/utils/test/event_validators/permission.py b/utils/test/event_validators/permission.py index 125f7c5c8..294b1302f 100644 --- a/utils/test/event_validators/permission.py +++ b/utils/test/event_validators/permission.py @@ -39,13 +39,12 @@ def validate_permission_create_event(event: EventDict, p: Permission, manager: s assert event["ChangePermissionManager"]["role"] == p.role, "Wrong role" assert event["ChangePermissionManager"]["manager"] == manager, "Wrong manager" - if emitted_by is not None: - assert convert.to_address(event["ChangePermissionManager"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["ChangePermissionManager"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_permission_revoke_event(event: EventDict, p: Permission, emitted_by: str = None, granted_from_agent: bool = False) -> None: @@ -63,10 +62,10 @@ def validate_permission_revoke_event(event: EventDict, p: Permission, emitted_by assert event["SetPermission"]["app"] == p.app, "Wrong app address" assert event["SetPermission"]["role"] == p.role, "Wrong role" assert event["SetPermission"]["allowed"] is False, "Wrong role" - if emitted_by is not None: - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_permission_grant_event(event: EventDict, p: Permission, emitted_by: str = None, granted_from_agent: bool = False) -> None: @@ -83,10 +82,10 @@ def validate_permission_grant_event(event: EventDict, p: Permission, emitted_by: assert event["SetPermission"]["app"] == p.app, "Wrong app address" assert event["SetPermission"]["role"] == p.role, "Wrong role" assert event["SetPermission"]["allowed"] is True, "Wrong allowed flag" - if emitted_by is not None: - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_permission_grantp_event(event: EventDict, p: Permission, params: List[Param], emitted_by: str = None) -> None: @@ -108,22 +107,18 @@ def validate_permission_grantp_event(event: EventDict, p: Permission, params: Li assert event["SetPermissionParams"]["role"] == p.role, "Wrong role" assert event["SetPermissionParams"]["paramsHash"] == params_hash - if emitted_by is not None: - assert convert.to_address(event["SetPermissionParams"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["SetPermissionParams"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" -def validate_grant_role_event(events: EventDict, role: str, grant_to: str, sender: str, emitted_by: str = None, is_dg_event: bool = False) -> None: +def validate_grant_role_event(events: EventDict, role: str, grant_to: str, sender: str, emitted_by: str = None) -> None: # this event chain is actual if grant role is forwarded through - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "RoleGranted", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "RoleGranted", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "RoleGranted", "ScriptResult", "Executed"] validate_events_chain([e.name for e in events], _events_chain) @@ -132,19 +127,16 @@ def validate_grant_role_event(events: EventDict, role: str, grant_to: str, sende assert events["RoleGranted"]["role"] == role, "Wrong role" assert events["RoleGranted"]["account"] == grant_to, "Wrong account" assert events["RoleGranted"]["sender"] == sender, "Wrong sender" - if emitted_by is not None: - assert convert.to_address(events["RoleGranted"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(events["RoleGranted"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_revoke_role_event( - events: EventDict, role: str, revoke_from: str, sender: str, emitted_by: str = None, is_dg_event: bool = False + events: EventDict, role: str, revoke_from: str, sender: str, emitted_by: str = None ) -> None: - if is_dg_event: - _events_chain = ["LogScriptCall", "LogScriptCall", "RoleRevoked", "ScriptResult", "Executed"] - else: - _events_chain = ["LogScriptCall", "LogScriptCall", "RoleRevoked", "ScriptResult"] + _events_chain = ["LogScriptCall", "LogScriptCall", "RoleRevoked", "ScriptResult", "Executed"] validate_events_chain([e.name for e in events], _events_chain) @@ -154,8 +146,7 @@ def validate_revoke_role_event( assert events["RoleRevoked"]["account"] == revoke_from, "Wrong account" assert events["RoleRevoked"]["sender"] == sender, "Wrong sender" - if emitted_by is not None: - assert convert.to_address(events["RoleRevoked"]["_emitted_by"]) == convert.to_address(emitted_by), "Wrong event emitter" + assert convert.to_address(events["RoleRevoked"]["_emitted_by"]) == convert.to_address(emitted_by), "Wrong event emitter" def validate_set_permission_manager_event(event: EventDict, app: str, role: str, manager: str, emitted_by: str = None) -> None: @@ -169,10 +160,10 @@ def validate_set_permission_manager_event(event: EventDict, app: str, role: str, assert event["ChangePermissionManager"]["app"] == app, "Wrong app address" assert event["ChangePermissionManager"]["role"] == role, "Wrong role" assert event["ChangePermissionManager"]["manager"] == manager, "Wrong manager" - if emitted_by is not None: - assert convert.to_address(event["ChangePermissionManager"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["ChangePermissionManager"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_dg_permission_revoke_event(event: EventDict, p: Permission, emitted_by: str = None) -> None: @@ -189,7 +180,7 @@ def validate_dg_permission_revoke_event(event: EventDict, p: Permission, emitted assert event["SetPermission"]["app"] == p.app, "Wrong app address" assert event["SetPermission"]["role"] == p.role, "Wrong role" assert event["SetPermission"]["allowed"] is False, "Wrong role" - if emitted_by is not None: - assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["SetPermission"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" diff --git a/utils/test/event_validators/proxy.py b/utils/test/event_validators/proxy.py index b7c19dcc6..2b63f7047 100644 --- a/utils/test/event_validators/proxy.py +++ b/utils/test/event_validators/proxy.py @@ -3,7 +3,7 @@ from .common import validate_events_chain -def validate_proxy_admin_changed(event: EventDict, prev_admin: str, new_admin: str, emitted_by: str = None) -> None: +def validate_proxy_admin_changed(event: EventDict, prev_admin: str, new_admin: str, emitted_by: str) -> None: _events_chain = ["LogScriptCall", "AdminChanged"] validate_events_chain([e.name for e in event], _events_chain) @@ -14,7 +14,21 @@ def validate_proxy_admin_changed(event: EventDict, prev_admin: str, new_admin: s assert event["AdminChanged"]["previousAdmin"] == prev_admin, "Wrong previous admin" assert event["AdminChanged"]["newAdmin"] == new_admin, "Wrong new admin" - if emitted_by is not None: - assert convert.to_address(event["AdminChanged"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["AdminChanged"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" + + +def validate_proxy_upgrade_event(event: EventDict, implementation: str, emitted_by: str): + _events_chain = ["LogScriptCall", "Upgraded", "ScriptResult", "Executed"] + validate_events_chain([e.name for e in event], _events_chain) + + assert event.count("LogScriptCall") == 1 + assert event.count("Upgraded") == 1 + + assert "Upgraded" in event, "No Upgraded event found" + + assert event["Upgraded"][0]["implementation"] == implementation, "Wrong implementation address" + + assert convert.to_address(event["Upgraded"][0]["_emitted_by"]) == convert.to_address( + emitted_by), "Wrong event emitter" diff --git a/utils/test/event_validators/relay_allowed_list.py b/utils/test/event_validators/relay_allowed_list.py index f068daadf..676baaa0d 100644 --- a/utils/test/event_validators/relay_allowed_list.py +++ b/utils/test/event_validators/relay_allowed_list.py @@ -11,8 +11,8 @@ def validate_relay_allowed_list_manager_set(event: EventDict, new_manager: str, assert event.count("ManagerChanged") == 1 assert event["ManagerChanged"]["new_manager"] == new_manager - if emitted_by is not None: - event_emitted_by = convert.to_address(event["ManagerChanged"]["_emitted_by"]) - assert event_emitted_by == convert.to_address( - emitted_by - ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" + + event_emitted_by = convert.to_address(event["ManagerChanged"]["_emitted_by"]) + assert event_emitted_by == convert.to_address( + emitted_by + ), f"Wrong event emitter {event_emitted_by} but expected {emitted_by}" diff --git a/utils/test/event_validators/rewards_manager.py b/utils/test/event_validators/rewards_manager.py index e28389b8b..84a2dd216 100644 --- a/utils/test/event_validators/rewards_manager.py +++ b/utils/test/event_validators/rewards_manager.py @@ -17,7 +17,7 @@ def validate_ownership_transferred_event(event: EventDict, ot: OwnershipTransfer assert event['OwnershipTransferred']['previousOwner'] == ot.previous_owner_addr assert event['OwnershipTransferred']['newOwner'] == ot.new_owner_addr - if emitted_by is not None: - assert convert.to_address(event["OwnershipTransferred"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + + assert convert.to_address(event["OwnershipTransferred"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" diff --git a/utils/test/event_validators/staking_router.py b/utils/test/event_validators/staking_router.py index b378f19a4..dbbac148c 100644 --- a/utils/test/event_validators/staking_router.py +++ b/utils/test/event_validators/staking_router.py @@ -49,28 +49,17 @@ def validate_staking_module_added_event(event: EventDict, module_item: StakingMo assert event["StakingModuleFeesSet"]["treasuryFee"] == module_item.treasury_fee -def validate_staking_module_update_event(event: EventDict, module_item: StakingModuleItem, emitted_by: str = None, is_dg_event: bool = False): - if is_dg_event: - _events_chain = [ - "LogScriptCall", - "LogScriptCall", - "StakingModuleShareLimitSet", - "StakingModuleFeesSet", - "StakingModuleMaxDepositsPerBlockSet", - "StakingModuleMinDepositBlockDistanceSet", - "ScriptResult", - "Executed" - ] - else: - _events_chain = [ - "LogScriptCall", - "LogScriptCall", - "StakingModuleShareLimitSet", - "StakingModuleFeesSet", - "StakingModuleMaxDepositsPerBlockSet", - "StakingModuleMinDepositBlockDistanceSet", - "ScriptResult", - ] +def validate_staking_module_update_event(event: EventDict, module_item: StakingModuleItem, emitted_by: str = None): + _events_chain = [ + "LogScriptCall", + "LogScriptCall", + "StakingModuleShareLimitSet", + "StakingModuleFeesSet", + "StakingModuleMaxDepositsPerBlockSet", + "StakingModuleMinDepositBlockDistanceSet", + "ScriptResult", + "Executed" + ] validate_events_chain([e.name for e in event], _events_chain) @@ -86,7 +75,6 @@ def validate_staking_module_update_event(event: EventDict, module_item: StakingM assert event["StakingModuleFeesSet"]["stakingModuleFee"] == module_item.module_fee assert event["StakingModuleFeesSet"]["treasuryFee"] == module_item.treasury_fee - if emitted_by is not None: - assert convert.to_address(event["StakingModuleFeesSet"]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["StakingModuleFeesSet"]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" diff --git a/utils/test/event_validators/time_constraints.py b/utils/test/event_validators/time_constraints.py index 346933126..c21b78afb 100644 --- a/utils/test/event_validators/time_constraints.py +++ b/utils/test/event_validators/time_constraints.py @@ -12,10 +12,9 @@ def validate_time_constraints_executed_before_event(event: EventDict, timestamp, assert event.count("TimeBeforeTimestampChecked") == 1 assert event["TimeBeforeTimestampChecked"][0]["timestamp"] == timestamp - if emitted_by is not None: - assert convert.to_address(event["TimeBeforeTimestampChecked"][0]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["TimeBeforeTimestampChecked"][0]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_dg_time_constraints_executed_before_event(event: EventDict, timestamp, emitted_by: str = None) -> None: @@ -26,10 +25,9 @@ def validate_dg_time_constraints_executed_before_event(event: EventDict, timesta assert event.count("TimeBeforeTimestampChecked") == 1 assert event["TimeBeforeTimestampChecked"][0]["timestamp"] == timestamp - if emitted_by is not None: - assert convert.to_address(event["TimeBeforeTimestampChecked"][0]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["TimeBeforeTimestampChecked"][0]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" def validate_dg_time_constraints_executed_within_day_time_event(event: EventDict, start_day_time, end_day_time, emitted_by: str = None) -> None: @@ -41,7 +39,6 @@ def validate_dg_time_constraints_executed_within_day_time_event(event: EventDict assert event["TimeWithinDayTimeChecked"][0]["startDayTime"] == start_day_time assert event["TimeWithinDayTimeChecked"][0]["endDayTime"] == end_day_time - if emitted_by is not None: - assert convert.to_address(event["TimeWithinDayTimeChecked"][0]["_emitted_by"]) == convert.to_address( - emitted_by - ), "Wrong event emitter" + assert convert.to_address(event["TimeWithinDayTimeChecked"][0]["_emitted_by"]) == convert.to_address( + emitted_by + ), "Wrong event emitter" diff --git a/utils/test/event_validators/unpause.py b/utils/test/event_validators/unpause.py index 10eb5e73a..218a3bb39 100644 --- a/utils/test/event_validators/unpause.py +++ b/utils/test/event_validators/unpause.py @@ -3,6 +3,7 @@ from typing import NamedTuple from brownie.network.event import EventDict +from brownie import convert from .common import validate_events_chain @@ -13,3 +14,16 @@ def validate_unpause_event(event: EventDict): validate_events_chain([e.name for e in event], _unpause_events_chain) assert event.count('DepositsUnpaused') == 1 + + +def validate_pause_for_event(events: EventDict, pause_for: int, sender: str, emitted_by: str): + # extra 'LogScriptCall' and 'ScriptResult' due to agent forwarding + _events_chain = ['LogScriptCall', 'LogScriptCall', 'Paused', 'ScriptResult', 'Executed'] + + validate_events_chain([e.name for e in events], _events_chain) + + assert events.count("Paused") == 1 + + assert events["Paused"]["duration"] == pause_for, "Wrong duration" + + assert convert.to_address(events["Paused"]["_emitted_by"]) == convert.to_address(emitted_by), "Wrong event emitter" diff --git a/utils/test/governance_helpers.py b/utils/test/governance_helpers.py index 497405e30..da4061c6d 100644 --- a/utils/test/governance_helpers.py +++ b/utils/test/governance_helpers.py @@ -10,18 +10,39 @@ def execute_vote(helpers, vote_ids_from_env): else: start_and_execute_votes(contracts.voting, helpers) +# TODO revert after December Aragon +#def execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env): +# if vote_ids_from_env and dg_proposal_ids_from_env: +# execute_vote(helpers, vote_ids_from_env) +# process_proposals(dg_proposal_ids_from_env) +# elif not vote_ids_from_env and dg_proposal_ids_from_env: +# process_proposals(dg_proposal_ids_from_env) +# else: +# proposals_count_before = contracts.emergency_protected_timelock.getProposalsCount() +# execute_vote(helpers, vote_ids_from_env) +# proposals_count_after = contracts.emergency_protected_timelock.getProposalsCount() +# if proposals_count_after == proposals_count_before: +# return +# new_proposal_ids = list(range(proposals_count_before + 1, proposals_count_after + 1)) +# process_proposals(new_proposal_ids) + def execute_vote_and_process_dg_proposals(helpers, vote_ids_from_env, dg_proposal_ids_from_env): - if vote_ids_from_env and dg_proposal_ids_from_env: - execute_vote(helpers, vote_ids_from_env) - process_proposals(dg_proposal_ids_from_env) - elif not vote_ids_from_env and dg_proposal_ids_from_env: - process_proposals(dg_proposal_ids_from_env) - else: - proposals_count_before = contracts.emergency_protected_timelock.getProposalsCount() - execute_vote(helpers, vote_ids_from_env) - proposals_count_after = contracts.emergency_protected_timelock.getProposalsCount() - if proposals_count_after == proposals_count_before: - return - new_proposal_ids = list(range(proposals_count_before + 1, proposals_count_after + 1)) - process_proposals(new_proposal_ids) + + # V1 + proposals_count_before1 = contracts.emergency_protected_timelock.getProposalsCount() + start_and_execute_votes(contracts.voting, helpers, 0) + proposals_count_after1 = contracts.emergency_protected_timelock.getProposalsCount() + new_proposal_ids1 = list(range(proposals_count_before1 + 1, proposals_count_after1 + 1)) + + # DG1 + process_proposals(new_proposal_ids1) + + # V2 + proposals_count_before2 = contracts.emergency_protected_timelock.getProposalsCount() + start_and_execute_votes(contracts.voting, helpers, 1) + proposals_count_after2 = contracts.emergency_protected_timelock.getProposalsCount() + new_proposal_ids2 = list(range(proposals_count_before2 + 1, proposals_count_after2 + 1)) + + # DG2 + process_proposals(new_proposal_ids2) diff --git a/utils/test/legacy_oracle_report_helpers.py b/utils/test/legacy_oracle_report_helpers.py deleted file mode 100644 index 025a985b2..000000000 --- a/utils/test/legacy_oracle_report_helpers.py +++ /dev/null @@ -1,12 +0,0 @@ -from brownie import interface, accounts -from utils.config import contracts, lido_dao_legacy_oracle - - -def legacy_report(_epochId, _beaconBalance, _beaconValidators): - oracle = interface.LidoOracle(lido_dao_legacy_oracle) - quorum = oracle.getQuorum() - members = oracle.getOracleMembers() - - for i in range(quorum): - member = accounts.at(members[i], force=True) - oracle.reportBeacon(_epochId, _beaconBalance, _beaconValidators, {"from": member}) diff --git a/utils/test/oracle_report_helpers.py b/utils/test/oracle_report_helpers.py index 6b4598493..e584f147f 100644 --- a/utils/test/oracle_report_helpers.py +++ b/utils/test/oracle_report_helpers.py @@ -20,6 +20,9 @@ EXTRA_DATA_FORMAT_EMPTY = 0 EXTRA_DATA_FORMAT_LIST = 1 +MOCK_VAULTS_DATA_TREE_ROOT = HexBytes(ZERO_HASH) +MOCK_VAULTS_DATA_TREE_CID = "test_vaults_data_tree_cid" + @dataclass class AccountingReport: @@ -37,6 +40,8 @@ class AccountingReport: withdrawalFinalizationBatches: list[int] simulatedShareRate: int isBunkerMode: bool + vaultsDataTreeRoot: HexBytes + vaultsDataTreeCid: str extraDataFormat: int extraDataHash: HexBytes extraDataItemsCount: int @@ -90,6 +95,8 @@ def prepare_accounting_report( [int(i) for i in withdrawalFinalizationBatches], int(simulatedShareRate), bool(isBunkerMode), + MOCK_VAULTS_DATA_TREE_ROOT, + MOCK_VAULTS_DATA_TREE_CID, int(extraDataFormat), extraDataHashList[0], int(extraDataItemsCount), @@ -296,20 +303,23 @@ def simulate_report( } try: - return contracts.lido.handleOracleReport.call( - reportTime, - ONE_DAY, - beaconValidators, - postCLBalance, - withdrawalVaultBalance, - elRewardsVaultBalance, - 0, - [], - 0, + calculatedValues = contracts.accounting.simulateOracleReport.call( + [ + reportTime, + ONE_DAY, + beaconValidators, + postCLBalance, + withdrawalVaultBalance, + elRewardsVaultBalance, + 0, + [], + 0, + ], {"from": contracts.accounting_oracle.address}, block_identifier=block_identifier, override=state_override, ) + return (calculatedValues[14], calculatedValues[13], calculatedValues[0], calculatedValues[1]) except VirtualMachineError: # workaround for empty revert message from ganache on eth_call @@ -321,16 +331,8 @@ def simulate_report( [contracts.accounting_oracle.address, override_slot, refSlot], ) - contracts.lido.handleOracleReport( - reportTime, - ONE_DAY, - beaconValidators, - postCLBalance, - withdrawalVaultBalance, - elRewardsVaultBalance, - 0, - [], - 0, + contracts.accounting.handleOracleReport( + [reportTime, ONE_DAY, beaconValidators, postCLBalance, withdrawalVaultBalance, elRewardsVaultBalance, 0, [], 0], {"from": contracts.accounting_oracle.address}, ) raise # unreachable, for static analysis only @@ -351,8 +353,7 @@ def wait_to_next_available_report_time(consensus_contract): else: raise - # Use chain.time() instead of block timestamp for consistency - time = chain.time() + time = web3.eth.get_block("latest").timestamp (_, EPOCHS_PER_FRAME, _) = consensus_contract.getFrameConfig() frame_start_with_offset = GENESIS_TIME + (refSlot + SLOTS_PER_EPOCH * EPOCHS_PER_FRAME + 1) * SECONDS_PER_SLOT chain.sleep(frame_start_with_offset - time) @@ -520,6 +521,8 @@ def oracle_report( withdrawalFinalizationBatches=withdrawalFinalizationBatches, simulatedShareRate=simulatedShareRate, isBunkerMode=is_bunker, + vaultsDataTreeRoot=MOCK_VAULTS_DATA_TREE_ROOT, + vaultsDataTreeCid=MOCK_VAULTS_DATA_TREE_CID, extraDataFormat=extraDataFormat, extraDataHash=extraDataHashList[0], extraDataItemsCount=extraDataItemsCount, diff --git a/utils/tx_tracing.py b/utils/tx_tracing.py index 06d465ced..568251c83 100644 --- a/utils/tx_tracing.py +++ b/utils/tx_tracing.py @@ -103,6 +103,72 @@ def validate_events_from_abis(): raise Exception(f"Event {topic} has different inputs in ABI") +def fix_duplicate_events(formatted_events, logs): + """ + Brownie's `_topics` object does not reliably handle events that share the same + signature across multiple interface files. When this happens, Brownie treats + them as indistinguishable: + + TargetValidatorsCountChanged event is populated over 4 contracts: + NodeOperatorsRegistry: + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode); + SDVT: + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode); + Sandbox: + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetValidatorsCount, uint256 targetLimitMode); + CSModule: + event TargetValidatorsCountChanged(uint256 indexed nodeOperatorId, uint256 targetLimitMode, uint256 targetValidatorsCount); + + Although these ABIs define the same event name and signature, Brownie collects + only one version of the event in its internal `_topics` map. Because ABI + parsing order is undefined and environment-dependent (verified experimentally), + the version that ends up in `_topics` may differ between runs. As a result, + the argument order inferred by Brownie for `TargetValidatorsCountChanged` + becomes inconsistent, breaking `eth_event.decode_logs` when decoding logs. + + To mitigate this, we explicitly append ordered parameters for each + encountered `TargetValidatorsCountChanged` event. This allows us to + validate arguments based on their positional order rather than relying + on potentially incorrect parameter names. + """ + + TARGET_TOPIC = web3.keccak(text='TargetValidatorsCountChanged(uint256,uint256,uint256)').hex().lower() + target_events = [] + + for i, log in enumerate(logs): + topics = log['topics'] or [] + if len(topics) == 0: + continue + if topics[0].lower() == TARGET_TOPIC: + target_events.append(i) + + for ev_index in target_events: + # 1st (indexed) uint256 argument value + formatted_events[ev_index]['data'].append({ + "name": "ARG0_VALUE", + "type": "uint256", + "value": int(logs[ev_index]['topics'][1], 16), + }) + + data_hex = logs[ev_index]['data'] + if data_hex.startswith("0x"): + data_hex = data_hex[2:] + + # 2nd (unindexed) uint256 argument value + formatted_events[ev_index]['data'].append({ + "name": "ARG1_VALUE", + "type": "uint256", + "value": int(data_hex[0:64], 16), + }) + + # 3rd (unindexed) uint256 argument value + formatted_events[ev_index]['data'].append({ + "name": "ARG2_VALUE", + "type": "uint256", + "value": int(data_hex[64:128], 16), + }) + + def tx_events_from_receipt(tx: TransactionReceipt) -> List: if not tx.status: raise "Tx has reverted status (set to 0)" @@ -122,7 +188,12 @@ def tx_events_from_receipt(tx: TransactionReceipt) -> List: log["data"] = data + "0" * missing_chars events = decode_logs(logs, _topics, allow_undecoded=True) - return [format_event(i) for i in events] + + formatted_events = [format_event(i) for i in events] + + fix_duplicate_events(formatted_events, logs) + + return formatted_events def tx_events_from_trace(tx: TransactionReceipt) -> Optional[List]: