From afc6e7b5aa1d5593830086733a0838979426b90f Mon Sep 17 00:00:00 2001 From: skhomuti Date: Tue, 24 Sep 2024 13:40:01 +0500 Subject: [PATCH 1/5] fix for staking modules test --- tests/acceptance/test_staking_router.py | 60 ++++++++++++++++++------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/tests/acceptance/test_staking_router.py b/tests/acceptance/test_staking_router.py index 00adcd673..8b9aa58eb 100644 --- a/tests/acceptance/test_staking_router.py +++ b/tests/acceptance/test_staking_router.py @@ -1,6 +1,11 @@ import pytest from brownie import interface, reverts # type: ignore +from configs.config_mainnet import ( + CS_MODULE_ID, CS_MODULE_MODULE_FEE_BP, + CS_MODULE_TREASURY_FEE_BP, CS_MODULE_TARGET_SHARE_BP, CS_MODULE_NAME, CS_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD, + CS_MODULE_MAX_DEPOSITS_PER_BLOCK, CS_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE, +) from utils.config import ( contracts, STAKING_ROUTER, @@ -95,22 +100,28 @@ def test_constants(contract): def test_staking_modules(contract): - assert contract.getStakingModulesCount() == 2 - - assert contract.getStakingModuleIds() == [CURATED_STAKING_MODULE_ID, SIMPLE_DVT_MODULE_ID] - assert contract.getStakingModuleIsActive(1) == True - assert contract.getStakingModuleIsStopped(1) == False - assert contract.getStakingModuleIsDepositsPaused(1) == False - assert contract.getStakingModuleNonce(1) >= 7260 - assert contract.getStakingModuleStatus(1) == 0 - - assert contract.getStakingModuleIsActive(2) == True - assert contract.getStakingModuleIsStopped(2) == False - assert contract.getStakingModuleIsDepositsPaused(2) == False - assert contract.getStakingModuleNonce(2) >= 0 - assert contract.getStakingModuleStatus(2) == 0 - - curated_module = contract.getStakingModule(1) + assert contract.getStakingModulesCount() == 3 + + assert contract.getStakingModuleIds() == [CURATED_STAKING_MODULE_ID, SIMPLE_DVT_MODULE_ID, CS_MODULE_ID] + assert contract.getStakingModuleIsActive(CURATED_STAKING_MODULE_ID) == True + assert contract.getStakingModuleIsStopped(CURATED_STAKING_MODULE_ID) == False + assert contract.getStakingModuleIsDepositsPaused(CURATED_STAKING_MODULE_ID) == False + assert contract.getStakingModuleNonce(CURATED_STAKING_MODULE_ID) >= 7260 + assert contract.getStakingModuleStatus(CURATED_STAKING_MODULE_ID) == 0 + + assert contract.getStakingModuleIsActive(SIMPLE_DVT_MODULE_ID) == True + assert contract.getStakingModuleIsStopped(SIMPLE_DVT_MODULE_ID) == False + assert contract.getStakingModuleIsDepositsPaused(SIMPLE_DVT_MODULE_ID) == False + assert contract.getStakingModuleNonce(SIMPLE_DVT_MODULE_ID) >= 0 + assert contract.getStakingModuleStatus(SIMPLE_DVT_MODULE_ID) == 0 + + assert contract.getStakingModuleIsActive(CS_MODULE_ID) == True + assert contract.getStakingModuleIsStopped(CS_MODULE_ID) == False + assert contract.getStakingModuleIsDepositsPaused(CS_MODULE_ID) == False + assert contract.getStakingModuleNonce(CS_MODULE_ID) >= 0 + assert contract.getStakingModuleStatus(CS_MODULE_ID) == 0 + + curated_module = contract.getStakingModule(CURATED_STAKING_MODULE_ID) assert curated_module["id"] == CURATED_STAKING_MODULE_ID assert curated_module["stakingModuleAddress"] == contracts.node_operators_registry assert curated_module["stakingModuleFee"] == CURATED_STAKING_MODULE_MODULE_FEE_BP @@ -125,7 +136,7 @@ def test_staking_modules(contract): assert curated_module["maxDepositsPerBlock"] == CURATED_STAKING_MODULE_MAX_DEPOSITS_PER_BLOCK assert curated_module["minDepositBlockDistance"] == CURATED_STAKING_MODULE_MIN_DEPOSITS_BLOCK_DISTANCE - simple_dvt_module = contract.getStakingModule(2) + simple_dvt_module = contract.getStakingModule(SIMPLE_DVT_MODULE_ID) assert simple_dvt_module["id"] == SIMPLE_DVT_MODULE_ID assert simple_dvt_module["stakingModuleAddress"] == contracts.simple_dvt assert simple_dvt_module["stakingModuleFee"] == SIMPLE_DVT_MODULE_MODULE_FEE_BP @@ -140,6 +151,21 @@ def test_staking_modules(contract): assert simple_dvt_module["maxDepositsPerBlock"] == SIMPLE_DVT_MODULE_MAX_DEPOSITS_PER_BLOCK assert curated_module["minDepositBlockDistance"] == SIMPLE_DVT_MODULE_MIN_DEPOSITS_BLOCK_DISTANCE + community_staking_module = contract.getStakingModule(CS_MODULE_ID) + assert community_staking_module["id"] == CS_MODULE_ID + assert community_staking_module["stakingModuleAddress"] == contracts.csm + assert community_staking_module["stakingModuleFee"] == CS_MODULE_MODULE_FEE_BP + assert community_staking_module["treasuryFee"] == CS_MODULE_TREASURY_FEE_BP + assert community_staking_module["stakeShareLimit"] == CS_MODULE_TARGET_SHARE_BP + assert community_staking_module["status"] == 0 + assert community_staking_module["name"] == CS_MODULE_NAME + assert community_staking_module["lastDepositAt"] > 0 + assert community_staking_module["lastDepositBlock"] > 0 + assert community_staking_module["exitedValidatorsCount"] >= 0 + assert community_staking_module["priorityExitShareThreshold"] == CS_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD + assert community_staking_module["maxDepositsPerBlock"] == CS_MODULE_MAX_DEPOSITS_PER_BLOCK + assert curated_module["minDepositBlockDistance"] == CS_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + fee_aggregate_distribution = contract.getStakingFeeAggregateDistribution() assert fee_aggregate_distribution["modulesFee"] <= SR_MODULES_FEE_E20 assert fee_aggregate_distribution["treasuryFee"] >= SR_TREASURY_FEE_E20 From 33f449c47fa3456ba431860dff6c10e1da5e3a9f Mon Sep 17 00:00:00 2001 From: skhomuti Date: Wed, 25 Sep 2024 12:13:21 +0500 Subject: [PATCH 2/5] add claim rewards tests --- interfaces/csm/CSFeeOracle.json | 2776 +++++++++++++-------------- tests/regression/test_csm.py | 83 +- utils/test/merkle_tree.py | 199 ++ utils/test/oracle_report_helpers.py | 46 +- 4 files changed, 1694 insertions(+), 1410 deletions(-) create mode 100644 utils/test/merkle_tree.py diff --git a/interfaces/csm/CSFeeOracle.json b/interfaces/csm/CSFeeOracle.json index 1b56f6a41..d08f0a893 100644 --- a/interfaces/csm/CSFeeOracle.json +++ b/interfaces/csm/CSFeeOracle.json @@ -1,1397 +1,1381 @@ [ - { - "type": "constructor", - "inputs": [ - { - "name": "secondsPerSlot", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "genesisTime", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "CONTRACT_MANAGER_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "DEFAULT_ADMIN_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "GENESIS_TIME", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "MANAGE_CONSENSUS_CONTRACT_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "MANAGE_CONSENSUS_VERSION_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "PAUSE_INFINITELY", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "PAUSE_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "RECOVERER_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "RESUME_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "SECONDS_PER_SLOT", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "SUBMIT_DATA_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "avgPerfLeewayBP", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "discardConsensusReport", - "inputs": [ - { - "name": "refSlot", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "feeDistributor", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "contract ICSFeeDistributor" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getConsensusContract", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getConsensusReport", - "inputs": [], - "outputs": [ - { - "name": "hash", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "refSlot", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "processingDeadlineTime", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "processingStarted", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getConsensusVersion", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getContractVersion", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getLastProcessingRefSlot", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getResumeSinceTimestamp", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRoleAdmin", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRoleMember", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "index", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getRoleMemberCount", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "grantRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "hasRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "initialize", - "inputs": [ - { - "name": "admin", - "type": "address", - "internalType": "address" - }, - { - "name": "feeDistributorContract", - "type": "address", - "internalType": "address" - }, - { - "name": "consensusContract", - "type": "address", - "internalType": "address" - }, - { - "name": "consensusVersion", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "_avgPerfLeewayBP", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "isPaused", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "pauseFor", - "inputs": [ - { - "name": "duration", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "pauseUntil", - "inputs": [ - { - "name": "pauseUntilInclusive", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "recoverERC1155", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "tokenId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "recoverERC20", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "recoverERC721", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "tokenId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "recoverEther", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "renounceRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "callerConfirmation", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "resume", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "revokeRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setConsensusContract", - "inputs": [ - { - "name": "addr", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setConsensusVersion", - "inputs": [ - { - "name": "version", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setFeeDistributorContract", - "inputs": [ - { - "name": "feeDistributorContract", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setPerformanceLeeway", - "inputs": [ - { - "name": "valueBP", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "submitConsensusReport", - "inputs": [ - { - "name": "reportHash", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "refSlot", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "deadline", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "submitReportData", - "inputs": [ - { - "name": "data", - "type": "tuple", - "internalType": "struct CSFeeOracle.ReportData", - "components": [ - { - "name": "consensusVersion", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "refSlot", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "treeRoot", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "treeCid", - "type": "string", - "internalType": "string" - }, - { - "name": "distributed", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "name": "contractVersion", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "supportsInterface", - "inputs": [ - { - "name": "interfaceId", - "type": "bytes4", - "internalType": "bytes4" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "event", - "name": "ConsensusHashContractSet", - "inputs": [ - { - "name": "addr", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "prevAddr", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ConsensusVersionSet", - "inputs": [ - { - "name": "version", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "prevVersion", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ContractVersionSet", - "inputs": [ - { - "name": "version", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ERC1155Recovered", - "inputs": [ - { - "name": "token", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "tokenId", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "recipient", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ERC20Recovered", - "inputs": [ - { - "name": "token", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "recipient", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ERC721Recovered", - "inputs": [ - { - "name": "token", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "tokenId", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "recipient", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "EtherRecovered", - "inputs": [ - { - "name": "recipient", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "FeeDistributorContractSet", - "inputs": [ - { - "name": "feeDistributorContract", - "type": "address", - "indexed": false, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Initialized", - "inputs": [ - { - "name": "version", - "type": "uint64", - "indexed": false, - "internalType": "uint64" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Paused", - "inputs": [ - { - "name": "duration", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "PerfLeewaySet", - "inputs": [ - { - "name": "valueBP", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ProcessingStarted", - "inputs": [ - { - "name": "refSlot", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "hash", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ReportDiscarded", - "inputs": [ - { - "name": "refSlot", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "hash", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ReportSettled", - "inputs": [ - { - "name": "refSlot", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "distributed", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "treeRoot", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - }, - { - "name": "treeCid", - "type": "string", - "indexed": false, - "internalType": "string" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "ReportSubmitted", - "inputs": [ - { - "name": "refSlot", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - }, - { - "name": "hash", - "type": "bytes32", - "indexed": false, - "internalType": "bytes32" - }, - { - "name": "processingDeadlineTime", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Resumed", - "inputs": [], - "anonymous": false - }, - { - "type": "event", - "name": "RoleAdminChanged", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "previousAdminRole", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "newAdminRole", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RoleGranted", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "sender", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RoleRevoked", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "sender", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "StETHSharesRecovered", - "inputs": [ - { - "name": "recipient", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "shares", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "WarnProcessingMissed", - "inputs": [ - { - "name": "refSlot", - "type": "uint256", - "indexed": true, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "AccessControlBadConfirmation", - "inputs": [] - }, - { - "type": "error", - "name": "AccessControlUnauthorizedAccount", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - }, - { - "name": "neededRole", - "type": "bytes32", - "internalType": "bytes32" - } - ] - }, - { - "type": "error", - "name": "AddressCannotBeSame", - "inputs": [] - }, - { - "type": "error", - "name": "AddressCannotBeZero", - "inputs": [] - }, - { - "type": "error", - "name": "FailedToSendEther", - "inputs": [] - }, - { - "type": "error", - "name": "HashCannotBeZero", - "inputs": [] - }, - { - "type": "error", - "name": "InitialRefSlotCannotBeLessThanProcessingOne", - "inputs": [ - { - "name": "initialRefSlot", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "processingRefSlot", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "InvalidContractVersionIncrement", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidInitialization", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidPerfLeeway", - "inputs": [] - }, - { - "type": "error", - "name": "NoConsensusReportToProcess", - "inputs": [] - }, - { - "type": "error", - "name": "NonZeroContractVersionOnInit", - "inputs": [] - }, - { - "type": "error", - "name": "NotAllowedToRecover", - "inputs": [] - }, - { - "type": "error", - "name": "NotInitializing", - "inputs": [] - }, - { - "type": "error", - "name": "PauseUntilMustBeInFuture", - "inputs": [] - }, - { - "type": "error", - "name": "PausedExpected", - "inputs": [] - }, - { - "type": "error", - "name": "ProcessingDeadlineMissed", - "inputs": [ - { - "name": "deadline", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "RefSlotAlreadyProcessing", - "inputs": [] - }, - { - "type": "error", - "name": "RefSlotCannotDecrease", - "inputs": [ - { - "name": "refSlot", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "prevRefSlot", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "RefSlotMustBeGreaterThanProcessingOne", - "inputs": [ - { - "name": "refSlot", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "processingRefSlot", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "ResumedExpected", - "inputs": [] - }, - { - "type": "error", - "name": "SafeCastOverflowedUintDowncast", - "inputs": [ - { - "name": "bits", - "type": "uint8", - "internalType": "uint8" - }, - { - "name": "value", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "SecondsPerSlotCannotBeZero", - "inputs": [] - }, - { - "type": "error", - "name": "SenderIsNotTheConsensusContract", - "inputs": [] - }, - { - "type": "error", - "name": "SenderNotAllowed", - "inputs": [] - }, - { - "type": "error", - "name": "UnexpectedChainConfig", - "inputs": [] - }, - { - "type": "error", - "name": "UnexpectedConsensusVersion", - "inputs": [ - { - "name": "expectedVersion", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "receivedVersion", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "UnexpectedContractVersion", - "inputs": [ - { - "name": "expected", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "received", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "UnexpectedDataHash", - "inputs": [ - { - "name": "consensusHash", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "receivedHash", - "type": "bytes32", - "internalType": "bytes32" - } - ] - }, - { - "type": "error", - "name": "UnexpectedRefSlot", - "inputs": [ - { - "name": "consensusRefSlot", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "dataRefSlot", - "type": "uint256", - "internalType": "uint256" - } - ] - }, - { - "type": "error", - "name": "VersionCannotBeSame", - "inputs": [] - }, - { - "type": "error", - "name": "ZeroAdminAddress", - "inputs": [] - }, - { - "type": "error", - "name": "ZeroFeeDistributorAddress", - "inputs": [] - }, - { - "type": "error", - "name": "ZeroPauseDuration", - "inputs": [] - } - ] + { + "inputs": [ + { + "internalType": "uint256", + "name": "secondsPerSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "genesisTime", + "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": [], + "name": "AddressCannotBeSame", + "type": "error" + }, + { + "inputs": [], + "name": "AddressCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "FailedToSendEther", + "type": "error" + }, + { + "inputs": [], + "name": "HashCannotBeZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "initialRefSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingRefSlot", + "type": "uint256" + } + ], + "name": "InitialRefSlotCannotBeLessThanProcessingOne", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidContractVersion", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidContractVersionIncrement", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPerfLeeway", + "type": "error" + }, + { + "inputs": [], + "name": "NoConsensusReportToProcess", + "type": "error" + }, + { + "inputs": [], + "name": "NonZeroContractVersionOnInit", + "type": "error" + }, + { + "inputs": [], + "name": "NotAllowedToRecover", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "PauseUntilMustBeInFuture", + "type": "error" + }, + { + "inputs": [], + "name": "PausedExpected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "ProcessingDeadlineMissed", + "type": "error" + }, + { + "inputs": [], + "name": "RefSlotAlreadyProcessing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "prevRefSlot", + "type": "uint256" + } + ], + "name": "RefSlotCannotDecrease", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingRefSlot", + "type": "uint256" + } + ], + "name": "RefSlotMustBeGreaterThanProcessingOne", + "type": "error" + }, + { + "inputs": [], + "name": "ResumedExpected", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [], + "name": "SecondsPerSlotCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "SenderIsNotTheConsensusContract", + "type": "error" + }, + { + "inputs": [], + "name": "SenderNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedChainConfig", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expectedVersion", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receivedVersion", + "type": "uint256" + } + ], + "name": "UnexpectedConsensusVersion", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "expected", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "received", + "type": "uint256" + } + ], + "name": "UnexpectedContractVersion", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "consensusHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "receivedHash", + "type": "bytes32" + } + ], + "name": "UnexpectedDataHash", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "consensusRefSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dataRefSlot", + "type": "uint256" + } + ], + "name": "UnexpectedRefSlot", + "type": "error" + }, + { + "inputs": [], + "name": "VersionCannotBeSame", + "type": "error" + }, + { + "inputs": [], + "name": "VersionCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAdminAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroFeeDistributorAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroPauseDuration", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "prevAddr", + "type": "address" + } + ], + "name": "ConsensusHashContractSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "version", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "prevVersion", + "type": "uint256" + } + ], + "name": "ConsensusVersionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "version", + "type": "uint256" + } + ], + "name": "ContractVersionSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ERC1155Recovered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ERC20Recovered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "ERC721Recovered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "EtherRecovered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "feeDistributorContract", + "type": "address" + } + ], + "name": "FeeDistributorContractSet", + "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": [ + { + "indexed": false, + "internalType": "uint256", + "name": "valueBP", + "type": "uint256" + } + ], + "name": "PerfLeewaySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + } + ], + "name": "ProcessingStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + } + ], + "name": "ReportDiscarded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "processingDeadlineTime", + "type": "uint256" + } + ], + "name": "ReportSubmitted", + "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": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "StETHSharesRecovered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + } + ], + "name": "WarnProcessingMissed", + "type": "event" + }, + { + "inputs": [], + "name": "CONTRACT_MANAGER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "GENESIS_TIME", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MANAGE_CONSENSUS_CONTRACT_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MANAGE_CONSENSUS_VERSION_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "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": "RECOVERER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RESUME_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SECONDS_PER_SLOT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SUBMIT_DATA_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "avgPerfLeewayBP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + } + ], + "name": "discardConsensusReport", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "feeDistributor", + "outputs": [ + { + "internalType": "contract ICSFeeDistributor", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConsensusContract", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConsensusReport", + "outputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "processingDeadlineTime", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "processingStarted", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getConsensusVersion", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getContractVersion", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastProcessingRefSlot", + "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" + }, + { + "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": "address", + "name": "feeDistributorContract", + "type": "address" + }, + { + "internalType": "address", + "name": "consensusContract", + "type": "address" + }, + { + "internalType": "uint256", + "name": "consensusVersion", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_avgPerfLeewayBP", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "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": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "recoverERC1155", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "recoverERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "recoverEther", + "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": "addr", + "type": "address" + } + ], + "name": "setConsensusContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "version", + "type": "uint256" + } + ], + "name": "setConsensusVersion", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "feeDistributorContract", + "type": "address" + } + ], + "name": "setFeeDistributorContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "valueBP", + "type": "uint256" + } + ], + "name": "setPerformanceLeeway", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "reportHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "submitConsensusReport", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "consensusVersion", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refSlot", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "treeRoot", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "treeCid", + "type": "string" + }, + { + "internalType": "string", + "name": "logCid", + "type": "string" + }, + { + "internalType": "uint256", + "name": "distributed", + "type": "uint256" + } + ], + "internalType": "struct CSFeeOracle.ReportData", + "name": "data", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "contractVersion", + "type": "uint256" + } + ], + "name": "submitReportData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/tests/regression/test_csm.py b/tests/regression/test_csm.py index 7b7dfd7a6..abcf62d30 100644 --- a/tests/regression/test_csm.py +++ b/tests/regression/test_csm.py @@ -11,7 +11,10 @@ from utils.test.csm_helpers import csm_add_node_operator, get_ea_member, csm_upload_keys from utils.test.deposits_helpers import fill_deposit_buffer from utils.test.helpers import ETH -from utils.test.oracle_report_helpers import oracle_report +from utils.test.oracle_report_helpers import ( + oracle_report, wait_to_next_available_report_time, prepare_csm_report, + reach_consensus, +) from utils.test.staking_router_helpers import set_staking_module_status, StakingModuleStatus contracts: ContractsLazyLoader = contracts @@ -34,29 +37,64 @@ def fee_distributor(): return contracts.cs_fee_distributor -@pytest.fixture() +@pytest.fixture(scope="module") +def fee_oracle(): + return contracts.cs_fee_oracle + + +@pytest.fixture def node_operator(csm, accounting) -> int: address, proof = get_ea_member() return csm_add_node_operator(csm, accounting, address, proof) -@pytest.fixture() +@pytest.fixture def pause_modules(): # pause deposit to all modules except csm - # to be sure that all deposits go to csm + # to be sure that all deposits goes to csm modules = contracts.staking_router.getStakingModules() for module in modules: if module[0] != CSM_MODULE_ID: set_staking_module_status(module[0], StakingModuleStatus.Stopped) -@pytest.fixture() +@pytest.fixture def deposits_to_csm(csm, pause_modules, node_operator): (_, _, depositable) = csm.getStakingModuleSummary() fill_deposit_buffer(depositable) contracts.lido.deposit(depositable, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) +@pytest.fixture +def ref_slot(): + wait_to_next_available_report_time(contracts.csm_hash_consensus) + ref_slot, _ = contracts.csm_hash_consensus.getCurrentFrame() + return ref_slot + + +@pytest.fixture +def distribute_reward_tree(deposits_to_csm, fee_oracle, fee_distributor, node_operator, ref_slot): + consensus_version = fee_oracle.getConsensusVersion() + oracle_version = fee_oracle.getContractVersion() + + rewards = ETH(0.05) + oracle_report(cl_diff=rewards) + distributed_shares = contracts.lido.sharesOf(fee_distributor) + assert distributed_shares > 0 + + report, report_hash, tree = prepare_csm_report({node_operator: distributed_shares}, ref_slot) + + submitter = reach_consensus( + ref_slot, + report_hash, + consensus_version, + contracts.csm_hash_consensus, + ) + + fee_oracle.submitReportData(report, oracle_version, {"from": submitter}) + return tree + + @pytest.mark.usefixtures("pause_modules") def test_deposit(node_operator, csm): (_, _, depositable_validators_count) = csm.getStakingModuleSummary() @@ -218,3 +256,38 @@ def test_csm_decrease_vetted_keys(csm, node_operator, stranger): no = csm.getNodeOperator(node_operator) assert no["totalVettedKeys"] == 1 + +@pytest.mark.usefixtures("deposits_to_csm") +def test_csm_claim_rewards_steth(csm, distribute_reward_tree, node_operator, fee_distributor): + tree = distribute_reward_tree.tree + shares = tree.values[0]["value"][1] + proof = list(tree.get_proof(tree.find(tree.leaf((node_operator, shares))))) + reward_address = csm.getNodeOperator(node_operator)["rewardAddress"] + shares_before = contracts.lido.sharesOf(reward_address) + + csm.claimRewardsStETH(node_operator, ETH(1), shares, proof, {"from": reward_address}) + # subtract 10 to avoid rounding errors + assert contracts.lido.sharesOf(reward_address) > shares_before + shares - 10 + +@pytest.mark.usefixtures("deposits_to_csm") +def test_csm_claim_rewards_wsteth(csm, distribute_reward_tree, node_operator, fee_distributor): + tree = distribute_reward_tree.tree + shares = tree.values[0]["value"][1] + proof = list(tree.get_proof(tree.find(tree.leaf((node_operator, shares))))) + reward_address = csm.getNodeOperator(node_operator)["rewardAddress"] + wsteth_before = contracts.wsteth.balanceOf(reward_address) + + csm.claimRewardsWstETH(node_operator, ETH(1), shares, proof, {"from": reward_address}) + assert contracts.wsteth.balanceOf(reward_address) > wsteth_before + +@pytest.mark.usefixtures("deposits_to_csm") +def test_csm_claim_rewards_eth(csm, distribute_reward_tree, node_operator, fee_distributor): + tree = distribute_reward_tree.tree + shares = tree.values[0]["value"][1] + proof = list(tree.get_proof(tree.find(tree.leaf((node_operator, shares))))) + reward_address = csm.getNodeOperator(node_operator)["rewardAddress"] + withdrawal_requests = contracts.withdrawal_queue.getWithdrawalRequests(reward_address) + + csm.claimRewardsUnstETH(node_operator, ETH(1), shares, proof, {"from": reward_address}) + + assert len(contracts.withdrawal_queue.getWithdrawalRequests(reward_address)) == len(withdrawal_requests) + 1 diff --git a/utils/test/merkle_tree.py b/utils/test/merkle_tree.py new file mode 100644 index 000000000..7abfe65a0 --- /dev/null +++ b/utils/test/merkle_tree.py @@ -0,0 +1,199 @@ +# copied from lido-oracle +import json +from abc import abstractmethod, ABC +from dataclasses import dataclass +from functools import reduce +from typing import Collection, Generic, Iterable, Sequence, TypedDict, TypeVar, TypeAlias + +from hexbytes import HexBytes +from eth_abi.abi import encode +from eth_hash.auto import keccak +from eth_typing import TypeStr + +Shares: TypeAlias = int +NodeOperatorId: TypeAlias = int +RewardTreeLeaf: TypeAlias = tuple[NodeOperatorId, Shares] + + +class TreeJSONEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, bytes): + return f"0x{o.hex()}" + return super().default(o) + +class MerkleTree(ABC): + """Merkle Tree interface""" + + @property + @abstractmethod + def root(self) -> bytes: ... + + @abstractmethod + def find(self, leaf: bytes) -> int: ... + + @abstractmethod + def get_proof(self, index: int) -> Iterable[bytes]: ... + + @classmethod + @abstractmethod + def verify(cls, root: bytes, leaf: bytes, proof: Iterable[bytes]) -> bool: ... + + @classmethod + @abstractmethod + def __hash_leaf__(cls, leaf: bytes) -> bytes: ... + + @classmethod + @abstractmethod + def __hash_node__(cls, lhs: bytes, rhs: bytes) -> bytes: ... + + +class CompleteBinaryMerkleTree(MerkleTree): + """The tree shaped as a [complete binary tree](https://xlinux.nist.gov/dads/HTML/completeBinaryTree.html).""" + + tree: tuple[bytes, ...] + + def __init__(self, leaves: Collection[bytes]): + if not leaves: + raise ValueError("Attempt to create an empty tree") + + tree = [b""] * (2 * len(leaves) - 1) + + for i, leaf in enumerate(leaves): + tree[len(tree) - 1 - i] = leaf + + for i in range(len(tree) - 1 - len(leaves), -1, -1): + tree[i] = self.__hash_node__(tree[2 * i + 1], tree[2 * i + 2]) + + self.tree = tuple(tree) + + @property + def root(self) -> bytes: + return self.tree[0] + + def find(self, leaf: bytes) -> int: + try: + return self.tree.index(leaf) + except ValueError as e: + raise ValueError("Node not found") from e + + def get_proof(self, index: int) -> Iterable[bytes]: + i = index + while i > 0: + yield self.tree[i - (-1) ** (i % 2)] + i = (i - 1) // 2 + + @classmethod + def verify(cls, root: bytes, leaf: bytes, proof: Iterable[bytes]) -> bool: + return reduce(lambda a, b: cls.__hash_node__(a, b), proof, leaf) == root + + +T = TypeVar("T", bound=Iterable) + + +class Value(TypedDict): + value: T + treeIndex: int + + +class Dump(TypedDict): + format: str + leafEncoding: Iterable[TypeStr] + tree: Collection[bytes] + values: Sequence[Value[T]] + + +class StandardMerkleTree(Generic[T], CompleteBinaryMerkleTree): + """ + OpenZeppelin Standard Merkle Tree + + - The tree is shaped as a complete binary tree. + - The leaves are sorted. + - The leaves are the result of ABI encoding a series of values. + - The hash used is Keccak256. + - The leaves are double-hashed to prevent second preimage attacks. + """ + + encoding: Iterable[TypeStr] + values: Sequence[Value[T]] + + FORMAT = "standard-v1" + + def __init__(self, values: Sequence[T], encoding: Iterable[TypeStr]): + self.encoding = encoding + + leaves = tuple(sorted(self.leaf(v) for v in values)) + super().__init__(leaves) + + self.values = tuple({"value": v, "treeIndex": self.find(self.leaf(v))} for v in values) + + def leaf(self, value: T) -> bytes: + return self.__hash_leaf__(encode(self.encoding, value)) + + def dump(self) -> Dump[T]: + return { + "format": self.FORMAT, + "leafEncoding": self.encoding, + "tree": self.tree, + "values": self.values, + } + + @classmethod + def load(cls, data: Dump[T]): + if "format" not in data or data["format"] != cls.FORMAT: + raise ValueError("Unexpected dump format value") + if "leafEncoding" not in data: + raise ValueError("No leaf encoding provided") + if "values" not in data: + raise ValueError("No values provided") + return cls([e["value"] for e in data["values"]], data["leafEncoding"]) + + @classmethod + def __hash_leaf__(cls, leaf: bytes) -> bytes: + return keccak(keccak(leaf)) + + @classmethod + def __hash_node__(cls, lhs: bytes, rhs: bytes) -> bytes: + if lhs > rhs: + lhs, rhs = rhs, lhs + return keccak(lhs + rhs) + + +@dataclass +class Tree: + """A wrapper around StandardMerkleTree to cover use cases of the CSM oracle""" + + tree: StandardMerkleTree[tuple[int, int]] + + @property + def root(self) -> HexBytes: + return HexBytes(self.tree.root) + + @classmethod + def decode(cls, content: bytes): + """Restore a tree from a supported binary representation""" + + try: + return cls(StandardMerkleTree.load(json.loads(content))) + except json.JSONDecodeError as e: + raise ValueError("Unsupported tree format") from e + + def encode(self) -> bytes: + """Convert the underlying StandardMerkleTree to a binary representation""" + + return ( + TreeJSONEncoder( + indent=None, + separators=(',', ':'), + sort_keys=True, + ) + .encode(self.dump()) + .encode() + ) + + def dump(self) -> Dump[RewardTreeLeaf]: + return self.tree.dump() + + @classmethod + def new(cls, values: Sequence[RewardTreeLeaf]): + """Create new instance around the wrapped tree out of the given values""" + return cls(StandardMerkleTree(values, ("uint256", "uint256"))) diff --git a/utils/test/oracle_report_helpers.py b/utils/test/oracle_report_helpers.py index 41edd0ed6..2433b0be7 100644 --- a/utils/test/oracle_report_helpers.py +++ b/utils/test/oracle_report_helpers.py @@ -11,14 +11,16 @@ from utils.config import (contracts, AO_CONSENSUS_VERSION) from utils.test.exit_bus_data import encode_data from utils.test.helpers import ETH, GWEI, eth_balance +from utils.test.merkle_tree import Tree ZERO_HASH = bytes([0] * 32) ZERO_BYTES32 = HexBytes(ZERO_HASH) ONE_DAY = 1 * 24 * 60 * 60 -SHARE_RATE_PRECISION = 10**27 +SHARE_RATE_PRECISION = 10 ** 27 EXTRA_DATA_FORMAT_EMPTY = 0 EXTRA_DATA_FORMAT_LIST = 1 + @dataclass class AccountingReport: """Accounting oracle ReportData struct""" @@ -57,7 +59,6 @@ def copy(self) -> "AccountingReport": return AccountingReport(*self.items) - def prepare_accounting_report( *, refSlot, @@ -107,6 +108,31 @@ def prepare_exit_bus_report(validators_to_exit, ref_slot): return report, report_hash +def prepare_csm_report(node_operators_rewards: dict, ref_slot): + consensus_version = contracts.cs_fee_oracle.getConsensusVersion() + shares = node_operators_rewards.copy() + if len(shares) < 2: + # put a stone + shares[2 ** 64 - 1] = 0 + + tree = Tree.new(tuple((no_id, amount) for (no_id, amount) in shares.items())) + # semi-random values + log_cid = web3.keccak(tree.root) + tree_cid = web3.keccak(log_cid) + + report = ( + consensus_version, + ref_slot, + tree.root, + str(tree_cid), + str(log_cid), + sum(shares.values()), + ) + report_data = encode_data_from_abi(report, contracts.cs_fee_oracle.abi, "submitReportData") + report_hash = web3.keccak(report_data) + return report, report_hash, tree + + def encode_data_from_abi(data, abi, func_name): report_function_abi = next(x for x in abi if x.get("name") == func_name) report_data_abi = report_function_abi["inputs"][0]["components"] # type: ignore @@ -117,7 +143,8 @@ def encode_data_from_abi(data, abi, func_name): def get_finalization_batches( share_rate: int, limited_withdrawal_vault_balance, limited_el_rewards_vault_balance ) -> list[int]: - (_, _, _, _, _, _, _, requestTimestampMargin, _, _, _, _) = contracts.oracle_report_sanity_checker.getOracleReportLimits() + (_, _, _, _, _, _, _, requestTimestampMargin, _, _, _, + _) = contracts.oracle_report_sanity_checker.getOracleReportLimits() buffered_ether = contracts.lido.getBufferedEther() unfinalized_steth = contracts.withdrawal_queue.unfinalizedStETH() reserved_buffer = min(buffered_ether, unfinalized_steth) @@ -168,7 +195,7 @@ def push_oracle_report( extraDataHashList=[ZERO_BYTES32], extraDataItemsCount=0, silent=False, - extraDataList:List[bytes]=[], + extraDataList: List[bytes] = [], ): if not silent: print(f"Preparing oracle report for refSlot: {refSlot}") @@ -192,7 +219,7 @@ def push_oracle_report( extraDataItemsCount=extraDataItemsCount, ) submitter = reach_consensus(refSlot, hash, consensusVersion, contracts.hash_consensus_for_accounting_oracle, silent) - accounts[0].transfer(submitter, 10**19) + accounts[0].transfer(submitter, 10 ** 19) # print(contracts.oracle_report_sanity_checker.getOracleReportLimits()) report_tx = contracts.accounting_oracle.submitReportData(items, oracleVersion, {"from": submitter}) if not silent: @@ -203,7 +230,8 @@ def push_oracle_report( if not silent: print("Submitted empty extra data report") else: - extra_report_tx_list = [contracts.accounting_oracle.submitReportExtraDataList(data, {"from": submitter}) for data in extraDataList] + extra_report_tx_list = [contracts.accounting_oracle.submitReportExtraDataList(data, {"from": submitter}) for + data in extraDataList] if not silent: print("Submitted NOT empty extra data report") @@ -294,7 +322,7 @@ def oracle_report( extraDataFormat=0, extraDataHashList=[ZERO_BYTES32], extraDataItemsCount=0, - extraDataList:List[bytes]=[], + extraDataList: List[bytes] = [], stakingModuleIdsWithNewlyExitedValidators=[], numExitedValidatorsByStakingModule=[], silent=False, @@ -322,7 +350,7 @@ def oracle_report( extraDataFormat=0, extraDataHashList=[ZERO_BYTES32], extraDataItemsCount=0, - extraDataList:List[bytes]=[], + extraDataList: List[bytes] = [], stakingModuleIdsWithNewlyExitedValidators=[], numExitedValidatorsByStakingModule=[], silent=False, @@ -350,7 +378,7 @@ def oracle_report( extraDataFormat=0, extraDataHashList=[ZERO_BYTES32], extraDataItemsCount=0, - extraDataList:List[bytes]=[], + extraDataList: List[bytes] = [], stakingModuleIdsWithNewlyExitedValidators=[], numExitedValidatorsByStakingModule=[], silent=False, From fd5e705427c8ac83fa39543647521314cabff72d Mon Sep 17 00:00:00 2001 From: skhomuti Date: Wed, 25 Sep 2024 12:36:07 +0500 Subject: [PATCH 3/5] fixes for review comments --- tests/acceptance/test_staking_router.py | 4 ++-- tests/regression/test_csm.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/acceptance/test_staking_router.py b/tests/acceptance/test_staking_router.py index 8b9aa58eb..f4f091816 100644 --- a/tests/acceptance/test_staking_router.py +++ b/tests/acceptance/test_staking_router.py @@ -149,7 +149,7 @@ def test_staking_modules(contract): assert simple_dvt_module["exitedValidatorsCount"] >= 0 assert simple_dvt_module["priorityExitShareThreshold"] == SIMPLE_DVT_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD assert simple_dvt_module["maxDepositsPerBlock"] == SIMPLE_DVT_MODULE_MAX_DEPOSITS_PER_BLOCK - assert curated_module["minDepositBlockDistance"] == SIMPLE_DVT_MODULE_MIN_DEPOSITS_BLOCK_DISTANCE + assert simple_dvt_module["minDepositBlockDistance"] == SIMPLE_DVT_MODULE_MIN_DEPOSITS_BLOCK_DISTANCE community_staking_module = contract.getStakingModule(CS_MODULE_ID) assert community_staking_module["id"] == CS_MODULE_ID @@ -164,7 +164,7 @@ def test_staking_modules(contract): assert community_staking_module["exitedValidatorsCount"] >= 0 assert community_staking_module["priorityExitShareThreshold"] == CS_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD assert community_staking_module["maxDepositsPerBlock"] == CS_MODULE_MAX_DEPOSITS_PER_BLOCK - assert curated_module["minDepositBlockDistance"] == CS_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE + assert community_staking_module["minDepositBlockDistance"] == CS_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE fee_aggregate_distribution = contract.getStakingFeeAggregateDistribution() assert fee_aggregate_distribution["modulesFee"] <= SR_MODULES_FEE_E20 diff --git a/tests/regression/test_csm.py b/tests/regression/test_csm.py index abcf62d30..f08141d83 100644 --- a/tests/regression/test_csm.py +++ b/tests/regression/test_csm.py @@ -213,6 +213,7 @@ def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_da @pytest.mark.usefixtures("deposits_to_csm") def test_csm_get_node_operator_summary(csm, node_operator, extra_data_service): + no = csm.getNodeOperator(node_operator) exited_keys = 1 stuck_keys = 1 extra_data = extra_data_service.collect({(CSM_MODULE_ID, node_operator): stuck_keys}, {(CSM_MODULE_ID, node_operator): exited_keys}, 2, 2) @@ -232,7 +233,7 @@ def test_csm_get_node_operator_summary(csm, node_operator, extra_data_service): assert summary["refundedValidatorsCount"] == 0 assert summary["stuckPenaltyEndTimestamp"] == 0 assert summary["totalExitedValidators"] == exited_keys - assert summary["totalDepositedValidators"] == 5 + assert summary["totalDepositedValidators"] == no["totalDepositedKeys"] assert summary["depositableValidatorsCount"] == 0 From 424625a879a4b780d758c2baa062f12c7c60bfe0 Mon Sep 17 00:00:00 2001 From: skhomuti Date: Thu, 26 Sep 2024 13:27:58 +0500 Subject: [PATCH 4/5] fixed extra data full list test added csm support optimized for better execution time --- tests/acceptance/test_csm.py | 2 +- tests/conftest.py | 2 +- tests/regression/conftest.py | 11 +- ...accounting_oracle_extra_data_full_items.py | 232 ++++++++++-------- tests/regression/test_csm.py | 4 +- .../test_staking_router_stake_distribution.py | 2 +- utils/node_operators.py | 1 - utils/test/csm_helpers.py | 19 +- 8 files changed, 163 insertions(+), 110 deletions(-) diff --git a/tests/acceptance/test_csm.py b/tests/acceptance/test_csm.py index 7d8a4b107..3ae8699d4 100644 --- a/tests/acceptance/test_csm.py +++ b/tests/acceptance/test_csm.py @@ -173,7 +173,7 @@ def test_initial_state(self, hash_consensus): # TODO uncomment this when initial ref slot is known # assert frame_config["initialEpoch"] > 5254400 / CHAIN_SLOTS_PER_EPOCH assert frame_config["epochsPerFrame"] == CS_ORACLE_EPOCHS_PER_FRAME - assert frame_config["fastLaneLengthSlots"] == 0 + assert frame_config["fastLaneLengthSlots"] == 1800 assert hash_consensus.getQuorum() == ORACLE_QUORUM diff --git a/tests/conftest.py b/tests/conftest.py index 7c530f5f2..399df9175 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -48,7 +48,7 @@ def ldo_holder(accounts): return accounts.at(LDO_HOLDER_ADDRESS_FOR_TESTS, force=True) -@pytest.fixture(scope="function") +@pytest.fixture(scope="session") def stranger(): return set_balance("0x98eC059dC3aDFbdd63429454aeB0C990fbA4a124", 100000) diff --git a/tests/regression/conftest.py b/tests/regression/conftest.py index 099afb95a..2dae114e9 100644 --- a/tests/regression/conftest.py +++ b/tests/regression/conftest.py @@ -14,7 +14,16 @@ @pytest.fixture(scope="function", autouse=is_there_any_vote_scripts() or is_there_any_upgrade_scripts()) -def autoexecute_vote(helpers, vote_ids_from_env, accounts, stranger): +def autoexecute_vote(request, helpers, vote_ids_from_env, accounts, stranger): + if "autoexecute_vote_ms" in request.node.fixturenames: + return + autoexecute_vote_impl(helpers, vote_ids_from_env, accounts, stranger) + +@pytest.fixture(scope="module") +def autoexecute_vote_ms(helpers, vote_ids_from_env, accounts, stranger): + autoexecute_vote_impl(helpers, vote_ids_from_env, accounts, stranger) + +def autoexecute_vote_impl(helpers, vote_ids_from_env, accounts, stranger): if vote_ids_from_env: helpers.execute_votes(accounts, vote_ids_from_env, contracts.voting, topup="0.5 ether") else: diff --git a/tests/regression/test_accounting_oracle_extra_data_full_items.py b/tests/regression/test_accounting_oracle_extra_data_full_items.py index 260613757..bd56000c6 100644 --- a/tests/regression/test_accounting_oracle_extra_data_full_items.py +++ b/tests/regression/test_accounting_oracle_extra_data_full_items.py @@ -3,6 +3,7 @@ from brownie.network.account import Account from brownie.network.web3 import Web3 +from utils.test.csm_helpers import csm_add_node_operator, csm_upload_keys from utils.test.deposits_helpers import fill_deposit_buffer from utils.test.helpers import shares_balance, almostEqWithDiff from utils.test.keys_helpers import random_pubkeys_batch, random_signatures_batch @@ -11,96 +12,121 @@ from utils.config import MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM from utils.config import contracts -from utils.test.simple_dvt_helpers import simple_dvt_add_node_operators, simple_dvt_add_keys, simple_dvt_vet_keys +NEW_KEYS_PER_OPERATOR = 2 -@pytest.fixture +pytestmark = pytest.mark.usefixtures("autoexecute_vote_ms") + +@pytest.fixture(scope="module") def voting_eoa(accounts): return accounts.at(contracts.voting.address, force=True) -@pytest.fixture +@pytest.fixture(scope="module") def agent_eoa(accounts): return accounts.at(contracts.agent.address, force=True) -@pytest.fixture +@pytest.fixture(scope="module") def evm_script_executor_eoa(accounts): return accounts.at(contracts.easy_track.evmScriptExecutor(), force=True) -@pytest.fixture +@pytest.fixture(scope="module") def nor(interface): return interface.NodeOperatorsRegistry(contracts.node_operators_registry.address) -@pytest.fixture +@pytest.fixture(scope="module") def sdvt(interface): return interface.SimpleDVT(contracts.simple_dvt.address) -@pytest.mark.parametrize( - ("nor_stuck_items", "nor_exited_items", "sdvt_stuck_items", "sdvt_exited_items"), - [ - (1, 1, 1, 1), - (1, 1, 1, 0), - (1, 1, 0, 1), - (1, 1, 0, 0), - (1, 0, 1, 1), - (1, 0, 1, 0), - (1, 0, 0, 1), - (1, 0, 0, 0), - (0, 1, 1, 1), - (0, 1, 1, 0), - (0, 1, 0, 1), - (0, 1, 0, 0), - (0, 0, 1, 1), - (0, 0, 1, 0), - (0, 0, 0, 1), - ] -) -def test_extra_data_full_items( - stranger, voting_eoa, agent_eoa, evm_script_executor_eoa, nor, sdvt, extra_data_service, - nor_stuck_items, nor_exited_items, sdvt_stuck_items, sdvt_exited_items -): - max_node_operators_per_item = MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM - new_keys_per_operator = 2 - +@pytest.fixture(scope="module") +def prepare_modules(nor, sdvt, voting_eoa, agent_eoa, evm_script_executor_eoa): # Fill NOR with new operators and keys (nor_count_before, added_nor_operators_count) = fill_nor_with_old_and_new_operators( nor, voting_eoa, agent_eoa, evm_script_executor_eoa, - new_keys_per_operator, - nor_stuck_items, - nor_exited_items, - max_node_operators_per_item, + NEW_KEYS_PER_OPERATOR, + MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, + ) + + (sdvt_count_before, added_sdvt_operators_count) = fill_nor_with_old_and_new_operators( + sdvt, + voting_eoa, + agent_eoa, + evm_script_executor_eoa, + NEW_KEYS_PER_OPERATOR, + MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, ) - # Fill SimpleDVT with new operators and keys - sdvt_operators_count = max(sdvt_stuck_items, sdvt_exited_items) * max_node_operators_per_item - add_sdvt_operators_with_keys(stranger, sdvt_operators_count, new_keys_per_operator) + # Fill CSM with new operators and keys + csm_operators_count = MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM + csm_count_before, added_csm_operators_count = fill_csm_operators_with_keys(csm_operators_count, + NEW_KEYS_PER_OPERATOR) # Deposit for new added keys from buffer - keys_for_sdvt = sdvt_operators_count * new_keys_per_operator keys_for_nor = 0 if added_nor_operators_count > 0: - keys_for_nor = (added_nor_operators_count * new_keys_per_operator) + (nor_count_before * new_keys_per_operator) + keys_for_nor = (added_nor_operators_count * NEW_KEYS_PER_OPERATOR) + (nor_count_before * NEW_KEYS_PER_OPERATOR) + keys_for_sdvt = 0 + if added_sdvt_operators_count > 0: + keys_for_sdvt = (added_sdvt_operators_count * NEW_KEYS_PER_OPERATOR) + ( + sdvt_count_before * NEW_KEYS_PER_OPERATOR) + keys_for_csm = 0 + if added_csm_operators_count > 0: + keys_for_csm = (added_csm_operators_count * NEW_KEYS_PER_OPERATOR) + (csm_count_before * NEW_KEYS_PER_OPERATOR) deposit_buffer_for_keys( contracts.staking_router, + keys_for_nor, keys_for_sdvt, - keys_for_nor + keys_for_csm, ) + +@pytest.mark.parametrize("nor_stuck_items", [1, 0]) +@pytest.mark.parametrize("nor_exited_items", [1, 0]) +@pytest.mark.parametrize("sdvt_stuck_items", [1, 0]) +@pytest.mark.parametrize("sdvt_exited_items", [1, 0]) +@pytest.mark.parametrize("csm_stuck_items", [1, 0]) +@pytest.mark.parametrize("csm_exited_items", [1, 0]) +@pytest.mark.usefixtures("prepare_modules") +def test_extra_data_full_items( + stranger, nor, sdvt, extra_data_service, + nor_stuck_items, nor_exited_items, sdvt_stuck_items, sdvt_exited_items, csm_stuck_items, csm_exited_items +): + if (nor_stuck_items + nor_exited_items + sdvt_stuck_items + sdvt_exited_items + csm_exited_items + csm_stuck_items) == 0: + pytest.skip("No items to report in this test case") + + nor_ids = [] + for i in range(0, nor.getNodeOperatorsCount()): + if nor.getNodeOperatorIsActive(i): + nor_ids.append(i) + sdvt_ids = [] + for i in range(0, sdvt.getNodeOperatorsCount()): + if sdvt.getNodeOperatorIsActive(i): + sdvt_ids.append(i) + csm_ids = range(0, MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM) + nor_ids_exited = nor_ids[:nor_exited_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM] + nor_ids_stuck = nor_ids[:nor_stuck_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM] + sdvt_ids_exited = sdvt_ids[:sdvt_exited_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM] + sdvt_ids_stuck = sdvt_ids[:sdvt_stuck_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM] + csm_ids_exited = csm_ids[:csm_exited_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM] + csm_ids_stuck = csm_ids[:csm_stuck_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM] + # Prepare report extra data - nor_stuck = {(1, i): 1 for i in range(0, nor_stuck_items * max_node_operators_per_item)} - nor_exited = {(1, i): nor.getNodeOperatorSummary(i)['totalExitedValidators'] + 1 for i in range(0, nor_exited_items * max_node_operators_per_item)} - sdvt_stuck = {(2, i): 1 for i in range(0, sdvt_stuck_items * max_node_operators_per_item)} - sdvt_exited = {(2, i): 1 for i in range(0, sdvt_exited_items * max_node_operators_per_item)} + nor_stuck = {(1, i): nor.getNodeOperatorSummary(i)['stuckValidatorsCount'] + 1 for i in nor_ids_stuck} + nor_exited = {(1, i): nor.getNodeOperatorSummary(i)['totalExitedValidators'] + 1 for i in nor_ids_exited} + sdvt_stuck = {(2, i): sdvt.getNodeOperatorSummary(i)['stuckValidatorsCount'] +1 for i in sdvt_ids_stuck} + sdvt_exited = {(2, i): sdvt.getNodeOperatorSummary(i)['totalExitedValidators'] + 1 for i in sdvt_ids_exited} + csm_stuck = {(3, i): contracts.csm.getNodeOperatorSummary(i)['stuckValidatorsCount'] + 1 for i in csm_ids_stuck} + csm_exited = {(3, i): contracts.csm.getNodeOperatorSummary(i)['totalExitedValidators'] + 1 for i in csm_ids_exited} extra_data = extra_data_service.collect( - {**nor_stuck, **sdvt_stuck}, - {**nor_exited, **sdvt_exited}, + {**nor_stuck, **sdvt_stuck, **csm_stuck}, + {**nor_exited, **sdvt_exited, **csm_exited}, MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, ) @@ -109,23 +135,31 @@ def test_extra_data_full_items( if nor_exited_items > 0: modules_with_exited.append(1) nor_exited_before = nor.getStakingModuleSummary()["totalExitedValidators"] - num_exited_validators_by_staking_module.append(nor_exited_before + (nor_exited_items * max_node_operators_per_item)) + num_exited_validators_by_staking_module.append(nor_exited_before + (nor_exited_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM)) if sdvt_exited_items > 0: modules_with_exited.append(2) - num_exited_validators_by_staking_module.append(sdvt_exited_items * max_node_operators_per_item) - - nor_balance_shares_before = [] - for i in range(0, len(nor_stuck)): - nor_balance_shares_before.append(shares_balance(nor.getNodeOperator(i, False)["rewardAddress"])) - sdvt_balance_shares_before = [] - for i in range(0, len(sdvt_stuck)): - sdvt_balance_shares_before.append(shares_balance(sdvt.getNodeOperator(i, False)["rewardAddress"])) + sdvt_exited_before = sdvt.getStakingModuleSummary()["totalExitedValidators"] + num_exited_validators_by_staking_module.append(sdvt_exited_before + (sdvt_exited_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM)) + if csm_exited_items > 0: + modules_with_exited.append(3) + csm_exited_before = contracts.csm.getStakingModuleSummary()["totalExitedValidators"] + num_exited_validators_by_staking_module.append(csm_exited_before + (csm_exited_items * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM)) + + nor_balance_shares_before = {} + for i in nor_ids_stuck: + nor_balance_shares_before[i] = shares_balance(nor.getNodeOperator(i, False)["rewardAddress"]) + sdvt_balance_shares_before = {} + for i in sdvt_ids_stuck: + sdvt_balance_shares_before[i] = shares_balance(sdvt.getNodeOperator(i, False)["rewardAddress"]) + csm_balance_shares_before = {} + for i in csm_ids_stuck: + csm_balance_shares_before[i] = shares_balance(contracts.csm.getNodeOperator(i)["rewardAddress"]) # Perform report - (report_tx, _) = oracle_report( + (report_tx, extra_report_tx_list) = oracle_report( extraDataFormat=1, extraDataHashList=extra_data.extra_data_hash_list, - extraDataItemsCount=(nor_exited_items + nor_stuck_items + sdvt_exited_items + sdvt_stuck_items), + extraDataItemsCount=(nor_exited_items + nor_stuck_items + sdvt_exited_items + sdvt_stuck_items + csm_exited_items + csm_stuck_items), extraDataList=extra_data.extra_data_list, stakingModuleIdsWithNewlyExitedValidators=modules_with_exited, numExitedValidatorsByStakingModule=num_exited_validators_by_staking_module, @@ -136,12 +170,12 @@ def test_extra_data_full_items( # Check NOR exited nor_penalty_shares = 0 - for i in range(0, len(nor_exited)): + for i in nor_ids_exited: assert nor.getNodeOperatorSummary(i)["totalExitedValidators"] == nor_exited[(1, i)] # Check NOR stuck. Check penalties and rewards if len(nor_stuck) > 0: nor_rewards = [e for e in report_tx.events["TransferShares"] if e['to'] == nor.address][0]['sharesValue'] - for i in range(0, len(nor_stuck)): + for i in nor_ids_stuck: assert nor.getNodeOperatorSummary(i)["stuckValidatorsCount"] == nor_stuck[(1, i)] assert nor.isOperatorPenalized(i) == True shares_after = shares_balance(nor.getNodeOperator(i, False)["rewardAddress"]) @@ -160,12 +194,12 @@ def test_extra_data_full_items( # Check SDVT exited sdvt_penalty_shares = 0 - for i in range(0, len(sdvt_exited)): + for i in sdvt_ids_exited: assert sdvt.getNodeOperatorSummary(i)["totalExitedValidators"] == sdvt_exited[(2, i)] # Check SDVT stuck. Check penalties and rewards if len(sdvt_stuck) > 0: sdvt_rewards = [e for e in report_tx.events["TransferShares"] if e['to'] == sdvt.address][0]['sharesValue'] - for i in range(0, len(sdvt_stuck)): + for i in sdvt_ids_stuck: assert sdvt.getNodeOperatorSummary(i)["stuckValidatorsCount"] == sdvt_stuck[(2, i)] assert sdvt.isOperatorPenalized(i) == True shares_after = shares_balance(sdvt.getNodeOperator(i, False)["rewardAddress"]) @@ -179,36 +213,21 @@ def test_extra_data_full_items( ) sdvt_penalty_shares += rewards_after // 2 - if sdvt_penalty_shares > 0: # TODO: Fix below check when contains other penalized node operators assert almostEqWithDiff(sum(e['amountOfShares'] for e in sdvt_distribute_reward_tx.events["StETHBurnRequested"]), sdvt_penalty_shares, 100) + # Check CSM exited + for i in csm_ids_exited: + assert contracts.csm.getNodeOperatorSummary(i)["totalExitedValidators"] == csm_exited[(3, i)] + # Check CSM stuck + for i in csm_ids_stuck: + assert contracts.csm.getNodeOperatorSummary(i)["stuckValidatorsCount"] == csm_stuck[(3, i)] + ############################################ # HELPER FUNCTIONS ############################################ - -def add_sdvt_operators_with_keys(enactor: Account, count: int, keys_per_operator: int): - names = [f"Name {i}" for i in range(0, count)] - reward_addresses = [f"0xab{str(i).zfill(38)}" for i in range(0, count)] - managers = [f"0xcd{str(i).zfill(38)}" for i in range(0, count)] - - node_operators_per_tx = 20 - for i in range(0, count, node_operators_per_tx): - simple_dvt_add_node_operators( - contracts.simple_dvt, - enactor, - [ - (names[j], reward_addresses[j], managers[j]) - for j in range(i, i + node_operators_per_tx) if j < count - ] - ) - for i in range(0, count): - simple_dvt_add_keys(contracts.simple_dvt, i, keys_per_operator) - simple_dvt_vet_keys(i, enactor) - - def add_nor_operators_with_keys(nor, voting_eoa: Account, evm_script_executor_eoa: Account, count: int, keys_per_operator: int): names = [f"Name {i}" for i in range(0, count)] reward_addresses = [f"0xbb{str(i).zfill(38)}" for i in range(0, count)] @@ -232,8 +251,27 @@ def add_nor_operators_with_keys(nor, voting_eoa: Account, evm_script_executor_eo nor.setNodeOperatorStakingLimit(no_id, keys_per_operator, {"from": evm_script_executor_eoa}) +def fill_csm_operators_with_keys(target_operators_count, keys_count): + if not contracts.csm.publicRelease(): + contracts.csm.grantRole(contracts.csm.MODULE_MANAGER_ROLE(), contracts.agent, {"from": contracts.agent}) + contracts.csm.activatePublicRelease({"from": contracts.agent}) + + csm_node_operators_before = contracts.csm.getNodeOperatorsCount() + added_operators_count = 0 + for no_id in range(0, csm_node_operators_before): + depositable_keys = contracts.csm.getNodeOperator(no_id)["depositableValidatorsCount"] + if depositable_keys < keys_count: + csm_upload_keys(contracts.csm, contracts.cs_accounting, no_id, depositable_keys - keys_count) + while csm_node_operators_before + added_operators_count < target_operators_count: + node_operator = f"0xbb{str(added_operators_count).zfill(38)}" + csm_add_node_operator(contracts.csm, contracts.cs_accounting, node_operator, [], keys_count=keys_count) + added_operators_count += 1 + return csm_node_operators_before, added_operators_count + + + def fill_nor_with_old_and_new_operators( - nor, voting_eoa, agent_eoa, evm_script_executor_eoa, new_keys_per_operator, nor_stuck_items, nor_exited_items, max_node_operators_per_item, + nor, voting_eoa, agent_eoa, evm_script_executor_eoa, new_keys_per_operator, max_node_operators_per_item, ) -> tuple[int, int]: # Curated: Add new operators and keys contracts.staking_router.grantRole( @@ -245,8 +283,8 @@ def fill_nor_with_old_and_new_operators( convert.to_uint(Web3.keccak(text="MANAGE_NODE_OPERATOR_ROLE")), {"from": contracts.voting}, ) - nor_count_before = nor.getNodeOperatorsCount() - added_nor_operators_count = (max(nor_stuck_items, nor_exited_items) * max_node_operators_per_item) - nor_count_before + nor_count_before = nor.getActiveNodeOperatorsCount() + added_nor_operators_count = max_node_operators_per_item - nor_count_before if added_nor_operators_count <= 0: return nor_count_before, added_nor_operators_count # Add new node operators and keys @@ -273,7 +311,7 @@ def fill_nor_with_old_and_new_operators( return nor_count_before, added_nor_operators_count -def deposit_buffer_for_keys(staking_router, sdvt_keys_to_deposit, nor_keys_to_deposit): +def deposit_buffer_for_keys(staking_router, nor_keys_to_deposit, sdvt_keys_to_deposit, csm_keys_to_deposit): total_depositable_keys = 0 module_digests = staking_router.getAllStakingModuleDigests() for digest in module_digests: @@ -284,16 +322,18 @@ def deposit_buffer_for_keys(staking_router, sdvt_keys_to_deposit, nor_keys_to_de contracts.lido.removeStakingLimit({"from": contracts.voting}) fill_deposit_buffer(total_depositable_keys) keys_per_deposit = 50 + # Deposits for NOR + times = round(nor_keys_to_deposit / keys_per_deposit) + for _ in range(0, times): + contracts.lido.deposit(keys_per_deposit, 1, "0x", {"from": contracts.deposit_security_module}) # Deposits for SDVT - times = sdvt_keys_to_deposit // keys_per_deposit + times = round(sdvt_keys_to_deposit / keys_per_deposit) for _ in range(0, times): contracts.lido.deposit(keys_per_deposit, 2, "0x", {"from": contracts.deposit_security_module}) - - # Deposits for NOR - times = nor_keys_to_deposit // keys_per_deposit; + # Deposits for CSM + times = round(csm_keys_to_deposit / keys_per_deposit) for _ in range(0, times): - contracts.lido.deposit(keys_per_deposit, 1, "0x", {"from": contracts.deposit_security_module}) - + contracts.lido.deposit(keys_per_deposit, 3, "0x", {"from": contracts.deposit_security_module}) def calc_no_rewards(module, no_id, shares_minted_as_fees): operator_summary = module.getNodeOperatorSummary(no_id) diff --git a/tests/regression/test_csm.py b/tests/regression/test_csm.py index f08141d83..1a1ae53fa 100644 --- a/tests/regression/test_csm.py +++ b/tests/regression/test_csm.py @@ -21,6 +21,7 @@ CSM_MODULE_ID = 3 +pytestmark = pytest.mark.usefixtures("autoexecute_vote_ms") @pytest.fixture(scope="module") def csm(): @@ -199,8 +200,7 @@ def test_csm_get_staking_module_summary(csm, accounting, node_operator, extra_da deposits_count = 3 new_keys = 5 new_depositable = new_keys - deposits_count - address, _ = get_ea_member() - csm_upload_keys(csm, accounting, node_operator, address, new_keys) + csm_upload_keys(csm, accounting, node_operator, new_keys) fill_deposit_buffer(deposits_count) contracts.lido.deposit(deposits_count, CSM_MODULE_ID, "0x", {"from": contracts.deposit_security_module}) diff --git a/tests/regression/test_staking_router_stake_distribution.py b/tests/regression/test_staking_router_stake_distribution.py index de1174ced..f827a7221 100644 --- a/tests/regression/test_staking_router_stake_distribution.py +++ b/tests/regression/test_staking_router_stake_distribution.py @@ -111,7 +111,7 @@ def assure_depositable_keys(stranger): fill_simple_dvt_ops_vetted_keys(stranger, 3, 5) if not modules[3].depositable_keys: address, proof = get_ea_member() - csm_add_node_operator(contracts.csm, contracts.cs_accounting, address, proof) + csm_add_node_operator(contracts.csm, contracts.cs_accounting, address, proof, curve_id=contracts.cs_early_adoption.CURVE_ID()) def test_stake_distribution(stranger): """ diff --git a/utils/node_operators.py b/utils/node_operators.py index 09848e33e..43715126f 100644 --- a/utils/node_operators.py +++ b/utils/node_operators.py @@ -49,4 +49,3 @@ def encode_add_operator_lido(address, name): def deactivate_node_operator(id: int) -> Tuple[str, str]: curated_sm = contracts.node_operators_registry return (curated_sm.address, curated_sm.deactivateNodeOperator.encode_input(id)) - diff --git a/utils/test/csm_helpers.py b/utils/test/csm_helpers.py index 56f23b67d..b93b641bb 100644 --- a/utils/test/csm_helpers.py +++ b/utils/test/csm_helpers.py @@ -1,6 +1,7 @@ from brownie import ZERO_ADDRESS -from utils.balance import set_balance +from utils.balance import set_balance_in_wei +from utils.test.helpers import ETH from utils.test.keys_helpers import random_pubkeys_batch, random_signatures_batch @@ -8,7 +9,7 @@ def get_ea_member(): """ Random address and proof for EA member """ - address = set_balance("0x00200f4e638e81ebe172daa18c9193a33a50bbbd", 100000) + address = "0x00200f4e638e81ebe172daa18c9193a33a50bbbd" proof = ["0x6afc021ded39a008e9e7c646cd70b0e0425b8c0cc2decc102d45a09a9cadc3b4", "0x9fb8ad314dcef15562b7a930e037068f77bb860156199862df2957017d68b59b", "0xa70999dbf9fb0843abbcfbc67498e195527ac129b43994dfaccdde3479893bed", @@ -26,10 +27,13 @@ def get_ea_member(): return address, proof -def csm_add_node_operator(csm, accounting, node_operator, proof, keys_count=5): +def csm_add_node_operator(csm, accounting, node_operator, proof, keys_count=5, curve_id=0): pubkeys_batch = random_pubkeys_batch(keys_count) signatures_batch = random_signatures_batch(keys_count) - curve_id = 1 # EA curve + + value = accounting.getBondAmountByKeysCount['uint256,uint256'](keys_count, curve_id) + set_balance_in_wei(node_operator, value + ETH(1)) + csm.addNodeOperatorETH( keys_count, pubkeys_batch, @@ -37,15 +41,16 @@ def csm_add_node_operator(csm, accounting, node_operator, proof, keys_count=5): (ZERO_ADDRESS, ZERO_ADDRESS, False), proof, ZERO_ADDRESS, - {"from": node_operator, "value": accounting.getBondAmountByKeysCount['uint256,uint256'](keys_count, curve_id)} + {"from": node_operator, "value": value} ) return csm.getNodeOperatorsCount() - 1 -def csm_upload_keys(csm, accounting, no_id, node_operator, keys_count=5): +def csm_upload_keys(csm, accounting, no_id, keys_count=5): + manager_address = csm.getNodeOperator(no_id)["managerAddress"] pubkeys_batch = random_pubkeys_batch(keys_count) signatures_batch = random_signatures_batch(keys_count) csm.addValidatorKeysETH(no_id, keys_count, pubkeys_batch, signatures_batch, - {"from": node_operator, "value": accounting.getRequiredBondForNextKeys(no_id, keys_count)} + {"from": manager_address, "value": accounting.getRequiredBondForNextKeys(no_id, keys_count)} ) From df164000fd34bbc778b8b2bed9a650b73b678c9f Mon Sep 17 00:00:00 2001 From: skhomuti Date: Fri, 27 Sep 2024 16:32:52 +0500 Subject: [PATCH 5/5] added test for worst extra data report in terms of gas usage --- ...accounting_oracle_extra_data_full_items.py | 89 +++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/tests/regression/test_accounting_oracle_extra_data_full_items.py b/tests/regression/test_accounting_oracle_extra_data_full_items.py index bd56000c6..b277a2b05 100644 --- a/tests/regression/test_accounting_oracle_extra_data_full_items.py +++ b/tests/regression/test_accounting_oracle_extra_data_full_items.py @@ -1,3 +1,5 @@ +from math import ceil + import pytest from brownie import convert from brownie.network.account import Account @@ -185,7 +187,7 @@ def test_extra_data_full_items( assert almostEqWithDiff( shares_after - nor_balance_shares_before[i], rewards_after // 2, - 1, + 2, ) nor_penalty_shares += rewards_after // 2 @@ -209,7 +211,7 @@ def test_extra_data_full_items( assert almostEqWithDiff( shares_after - sdvt_balance_shares_before[i], rewards_after // 2, - 1, + 2, ) sdvt_penalty_shares += rewards_after // 2 @@ -224,6 +226,82 @@ def test_extra_data_full_items( for i in csm_ids_stuck: assert contracts.csm.getNodeOperatorSummary(i)["stuckValidatorsCount"] == csm_stuck[(3, i)] + +def test_extra_data_most_expensive_report(autoexecute_vote_ms, extra_data_service): + """ + Make sure the worst report fits into the block gas limit. + It needs to prepare a lot of node operators in a very special state, so it takes a lot of time to run. + + N = oracle limit + - Create N NOs + - Deposit all keys + - Upload +1 key for each NO + - Create New NO with 1 key + - Stuck N NOs + - Deposit 1 key from NO N+1 (to exclude batches from the queue) + - Unstuck N NOs + + An estimate for 8 * 24 items: + Gas used: 11850807 (39.50%) + """ + + csm_operators_count = MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION * MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM + # create or ensure there are max node operators with 1 depositable key + fill_csm_operators_with_keys(csm_operators_count,1) + depositable_keys = contracts.csm.getStakingModuleSummary()["depositableValidatorsCount"] + deposit_buffer_for_keys(contracts.staking_router, 0, 0, depositable_keys) + assert contracts.csm.getStakingModuleSummary()["depositableValidatorsCount"] == 0 + + csm_ids = range(0, csm_operators_count) + # Upload a new key for each node operator to put them into the queue + for i in csm_ids: + csm_upload_keys(contracts.csm, contracts.cs_accounting, i, 1) + assert contracts.csm.getNodeOperator(i)["depositableValidatorsCount"] == 1 + # Add a new node operator with 1 depositable key to the end of the queue + last_no_id = csm_add_node_operator(contracts.csm, contracts.cs_accounting, f"0xbb{str(csm_operators_count).zfill(38)}", [], keys_count=1) + # report stuck keys for all node operators + extra_data = extra_data_service.collect( + {(3, i): 1 for i in range(csm_operators_count)}, + {}, + MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, + MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, + ) + + oracle_report( + extraDataFormat=1, + extraDataHashList=extra_data.extra_data_hash_list, + extraDataItemsCount=MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, + extraDataList=extra_data.extra_data_list, + ) + + # Check CSM stuck + for i in csm_ids: + assert contracts.csm.getNodeOperatorSummary(i)["stuckValidatorsCount"] == 1 + # deposit last node operator's key + deposit_buffer_for_keys(contracts.staking_router, 0, 0, 1) + assert contracts.csm.getNodeOperator(last_no_id)["totalDepositedKeys"] == 1 + + # report unstuck keys for all node operators + + extra_data = extra_data_service.collect( + {(3, i): 0 for i in range(csm_operators_count)}, + {}, + MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, + MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, + ) + + oracle_report( + extraDataFormat=1, + extraDataHashList=extra_data.extra_data_hash_list, + extraDataItemsCount=MAX_ITEMS_PER_EXTRA_DATA_TRANSACTION, + extraDataList=extra_data.extra_data_list, + ) + + # Check CSM unstuck + for i in csm_ids: + assert contracts.csm.getNodeOperatorSummary(i)["stuckValidatorsCount"] == 0 + + ############################################ # HELPER FUNCTIONS ############################################ @@ -262,6 +340,7 @@ def fill_csm_operators_with_keys(target_operators_count, keys_count): depositable_keys = contracts.csm.getNodeOperator(no_id)["depositableValidatorsCount"] if depositable_keys < keys_count: csm_upload_keys(contracts.csm, contracts.cs_accounting, no_id, depositable_keys - keys_count) + assert contracts.csm.getNodeOperator(no_id)["depositableValidatorsCount"] == keys_count while csm_node_operators_before + added_operators_count < target_operators_count: node_operator = f"0xbb{str(added_operators_count).zfill(38)}" csm_add_node_operator(contracts.csm, contracts.cs_accounting, node_operator, [], keys_count=keys_count) @@ -323,15 +402,15 @@ def deposit_buffer_for_keys(staking_router, nor_keys_to_deposit, sdvt_keys_to_de fill_deposit_buffer(total_depositable_keys) keys_per_deposit = 50 # Deposits for NOR - times = round(nor_keys_to_deposit / keys_per_deposit) + times = ceil(nor_keys_to_deposit / keys_per_deposit) for _ in range(0, times): contracts.lido.deposit(keys_per_deposit, 1, "0x", {"from": contracts.deposit_security_module}) # Deposits for SDVT - times = round(sdvt_keys_to_deposit / keys_per_deposit) + times = ceil(sdvt_keys_to_deposit / keys_per_deposit) for _ in range(0, times): contracts.lido.deposit(keys_per_deposit, 2, "0x", {"from": contracts.deposit_security_module}) # Deposits for CSM - times = round(csm_keys_to_deposit / keys_per_deposit) + times = ceil(csm_keys_to_deposit / keys_per_deposit) for _ in range(0, times): contracts.lido.deposit(keys_per_deposit, 3, "0x", {"from": contracts.deposit_security_module})