diff --git a/configs/config_mainnet.py b/configs/config_mainnet.py index ef31f368..7f93f076 100644 --- a/configs/config_mainnet.py +++ b/configs/config_mainnet.py @@ -323,3 +323,25 @@ TRP_FACTORY_DEPLOY_BLOCK_NUMBER = 16540381 SDVT_APP_ID = "0xe1635b63b5f7b5e545f2a637558a4029dea7905361a2f0fc28c66e9136cf86a4" + +#CSM +CS_ORACLE_EPOCHS_PER_FRAME = 225 * 7 # 7 days +CS_MODULE_ID = 3 +CS_MODULE_NAME = "Community Staking" +CS_MODULE_MODULE_FEE_BP = 600 +CS_MODULE_TREASURY_FEE_BP = 400 +CS_MODULE_TARGET_SHARE_BP = 100 +CS_MODULE_PRIORITY_EXIT_SHARE_THRESHOLD = 100 +CS_MODULE_MAX_DEPOSITS_PER_BLOCK = 30 +CS_MODULE_MIN_DEPOSIT_BLOCK_DISTANCE = 25 + +# ToDo: update after deploy +CSM_ADDRESS = "0xbc8eCccb89650c3E796e803CB009BF9b898CB359" +CS_ACCOUNTING_ADDRESS = "0x4B0FccF53589c1F185B35db88bB315a0bBF9a3e0" +CS_ORACLE_HASH_CONSENSUS_ADDRESS = "0xD537bF4b795b7D07Bd5F4bAf7017e3ce8360B1DE" +CS_EARLY_ADOPTION_ADDRESS = "0x1aa96efd2B002541E830CB7f60e473AA24e31F9A" +CS_FEE_DISTRIBUTOR_ADDRESS = "0x5847798CE8c89e3Fff59AE5fA30BEC0d406b5687" +CS_FEE_ORACLE_ADDRESS = "0x4e3E7dC9D84dA7BE8f017f4C36153A61341736d4" +CS_GATE_SEAL_ADDRESS = "0x5cFCa30450B1e5548F140C24A47E36c10CE306F0" +CS_VERIFIER_ADDRESS = "0x0C60536783db9ED5A2B216970B10FF2243d317dD" + diff --git a/interfaces/csm/CSFeeOracle.json b/interfaces/csm/CSFeeOracle.json index 1b56f6a4..d08f0a89 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/acceptance/test_csm.py b/tests/acceptance/test_csm.py index 7d8a4b10..357268c0 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 @@ -181,7 +181,7 @@ def test_initial_state(self, hash_consensus): # assert hash_consensus.getInitialRefSlot() > 5254400 members = hash_consensus.getMembers() - assert members["addresses"] == ORACLE_COMMITTEE + assert sorted(members["addresses"]) == sorted(ORACLE_COMMITTEE) assert hash_consensus.getReportProcessor() == CS_FEE_ORACLE_ADDRESS diff --git a/tests/acceptance/test_staking_router.py b/tests/acceptance/test_staking_router.py index 00adcd67..f4f09181 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 @@ -138,7 +149,22 @@ 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 + 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 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/conftest.py b/tests/conftest.py index 9bb9d5f1..137118b6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -47,7 +47,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 099afb95..2dae114e 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 8dad538d..70a83f04 100644 --- a/tests/regression/test_accounting_oracle_extra_data_full_items.py +++ b/tests/regression/test_accounting_oracle_extra_data_full_items.py @@ -1,8 +1,11 @@ +from math import ceil + import pytest from brownie import convert 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,95 +14,110 @@ 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 +@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(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( - voting_eoa, evm_script_executor_eoa, nor, sdvt, extra_data_service, - nor_stuck_items, nor_exited_items, sdvt_stuck_items, sdvt_exited_items, stranger -): - 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, autoexecute_vote_ms): # Fill NOR with new operators and keys (nor_count_before, added_nor_operators_count) = fill_nor_with_old_and_new_operators( nor, voting_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, ) - # Fill SimpleDVT with new operators and keys (sdvt_count_before, added_sdvt_operators_count) = fill_nor_with_old_and_new_operators( sdvt, voting_eoa, evm_script_executor_eoa, - new_keys_per_operator, - sdvt_stuck_items, - sdvt_exited_items, - max_node_operators_per_item, + NEW_KEYS_PER_OPERATOR, + MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM, ) + # 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 = (added_sdvt_operators_count * new_keys_per_operator) + (sdvt_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 = (added_sdvt_operators_count * NEW_KEYS_PER_OPERATOR) + (sdvt_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_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): sdvt.getNodeOperatorSummary(i)['totalExitedValidators'] + 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, ) @@ -108,24 +126,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) sdvt_exited_before = sdvt.getStakingModuleSummary()["totalExitedValidators"] - num_exited_validators_by_staking_module.append(sdvt_exited_before + (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"])) + 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 +161,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"]) @@ -151,7 +176,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 @@ -160,12 +185,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"]) @@ -175,15 +200,97 @@ 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 - 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)] + + +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 ############################################ @@ -213,8 +320,28 @@ 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) + 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) + added_operators_count += 1 + return csm_node_operators_before, added_operators_count + + + def fill_nor_with_old_and_new_operators( - nor, voting_eoa, evm_script_executor_eoa, new_keys_per_operator, nor_stuck_items, nor_exited_items, max_node_operators_per_item, + nor, voting_eoa, evm_script_executor_eoa, new_keys_per_operator, max_node_operators_per_item, ) -> tuple[int, int]: contracts.acl.grantPermission( contracts.voting, @@ -225,8 +352,7 @@ def fill_nor_with_old_and_new_operators( # Calculate new operators count operators_count_before = nor.getNodeOperatorsCount() - operators_count_wanted = max(nor_stuck_items, nor_exited_items) * max_node_operators_per_item - operators_count_after = max(operators_count_wanted, operators_count_before) + operators_count_after = max(max_node_operators_per_item, operators_count_before) operators_count_added = max(operators_count_after - operators_count_before, 0) # Add new node operators and keys @@ -269,7 +395,7 @@ def fill_nor_with_old_and_new_operators( return operators_count_before, operators_count_added -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: @@ -280,16 +406,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 = 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 = 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 NOR - times = nor_keys_to_deposit // keys_per_deposit; + # Deposits for CSM + times = ceil(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 7b7dfd7a..1a1ae53f 100644 --- a/tests/regression/test_csm.py +++ b/tests/regression/test_csm.py @@ -11,13 +11,17 @@ 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 CSM_MODULE_ID = 3 +pytestmark = pytest.mark.usefixtures("autoexecute_vote_ms") @pytest.fixture(scope="module") def csm(): @@ -34,29 +38,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() @@ -161,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}) @@ -175,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) @@ -194,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 @@ -218,3 +257,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/tests/regression/test_staking_router_stake_distribution.py b/tests/regression/test_staking_router_stake_distribution.py index de1174ce..f827a722 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 09848e33..43715126 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 56f23b67..8df52baa 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,28 +9,32 @@ def get_ea_member(): """ Random address and proof for EA member """ - address = set_balance("0x00200f4e638e81ebe172daa18c9193a33a50bbbd", 100000) - proof = ["0x6afc021ded39a008e9e7c646cd70b0e0425b8c0cc2decc102d45a09a9cadc3b4", - "0x9fb8ad314dcef15562b7a930e037068f77bb860156199862df2957017d68b59b", - "0xa70999dbf9fb0843abbcfbc67498e195527ac129b43994dfaccdde3479893bed", - "0xe529a4c4315a65bc576d6cb030bb0f6957084d18fb108d8bb2d2b9ac05b17d66", - "0xc94fe8c075792d6610715e8adbc53fafd06aeddaeb4fcb45867d8ab92685bbe6", - "0xb2cc15dba514f0df05e7243a48aa47b1a5455f5a4291398c0edacca5f0aca2fc", - "0x7c4efc55c91a8f3b6064dcdb0622d9815dc31998fc6f9069a6ba0188512f1144", - "0xcb497013c0ad8e40663b8318967dcbc1cfbb38132a5bb36a6def0bf1352b3733", - "0xab3d7faaf4662097e8a953ee63af9e71078a4224c4c69425b6c53404aa14459f", - "0x9e1485ad05559cc88dc80939a490c03bbdb7aa81c01b259023c7cdc73884dabc", - "0x6206a54be4267fcced2b6c60e90b9f1974933b4655444a56ae993d4727bacde4", - "0x60aaa2f08de8edbe2b9edfb201c5ed962cbbd8ce1d096013549fc4de2dc330d6", - "0xd88ad1a0d41adf346661f16fad68e45102a5f7730122af03c81bdc90a6b473d7", - "0x9abd3efc8d538c4714dae174a75caaa1414c9a9155e8cea71e977822978807df"] + address = "0x00200f4e638e81ebe172daa18c9193a33a50bbbd" + proof = ["0x6afb48863bdb84141ef424715c70cd61e5d629a293038b2e55b2aaa330955435", + "0xf2cbc3e761642defc881539fb26fd2f349c87ac9baa031572693d4b47286d521", + "0x526c3d791066e220842b26d606a332cc22d088eb0d1431c43d6c5a8503417f4e", + "0x25f371f22e592b9541b1fe8c3e2a599b3db0b11e9b027bffe525dc48b6971bf5", + "0xffaad8403c4681c00ce2a7d6c82f2f4343ec7ef0efd9dd1959e37a55c71949bc", + "0xf86d0c08277b41ad7d4b9640be4c1e6b0e2eed1fd4bfc084c300086cb638b933", + "0x3c66b0db484c5bd7351114a5e7fb073604fd8772254ad3ef02432918a7b65f2c", + "0x47d2ab6d81d882809ffb9df212c126fbbb5fe01c0849cb42d8101fe0f5b5ea1c", + "0x62373951ea824814af660ae6276d03fec5e734f9ea8e9fe662e600b19fd58dea", + "0x812242ec081e8ebf25e682ff968a40dbf0786caa89ba18958d7f91f7e981e415", + "0x1b3e4aade81953dbfc7df20d22852a95c255bf0bcf5f186115f9f439b29f4845", + "0x2db100e320128d95cf46a003497bc5dcf25cdc62a1e66a89f0fa7c79b1a858ad", + "0x03d33582f7cac74515d95d4ab3711d95ce9814df7351a1a46223d3bb4c3fdd44", + "0x566d2db5a4091a4c3152e2e3ba7a26d44b9f09515d4d8e587cf7f8aea77e38f0", + "0xd415d9673b0b81ccf04e5ed1317f6a2ec5c94bc3c8d14e5d302225ad9b99b137"] 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 +42,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)} ) diff --git a/utils/test/merkle_tree.py b/utils/test/merkle_tree.py new file mode 100644 index 00000000..7abfe65a --- /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 41edd0ed..2433b0be 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,