diff --git a/README.md b/README.md index 5f0c08b..d8967ea 100644 --- a/README.md +++ b/README.md @@ -217,3 +217,111 @@ As the recipient address is deterministic, we can compute this address before pu To compute this address, call `function getCLFeeRecipient(bytes calldata _publicKey) view` on the `StakingContract`. +## Diagrams + +### Overview of the system + +```mermaid +flowchart TB + subgraph Actors + U[[User]] + O[[Operator]] + A[[Admin]] + end + + subgraph Recipients + RA[Recipient Contract A] + RB[Recipient Contract B] + end + + subgraph Consensus Layer + VA[Validator A] + VB[Validator B] + end + + Dispatcher + StakingContract + + RI[Recipient Implementation] + DepositContract + + DepositContract--Fund-->VA & VB + + U--Stake ETH-->StakingContract + O--Add/Manage keys-->StakingContract + A--Approve keys-->StakingContract + StakingContract--Deposit funded validators-->DepositContract + StakingContract-.- RI --Clone--> RA & RB + + U--Exit-->StakingContract + U--Withdraw-->StakingContract + + StakingContract--Withdraw--> RB + RB--Withdraw--> Dispatcher + + Dispatcher--Take commission--> OT[Operator Treasury] + Dispatcher--Take commission--> IT[Integrator Treasury] + Dispatcher--Withdraw--> U + +``` + +### Deposit Flow + +```mermaid +sequenceDiagram + actor U as User Wallet + U->>Staking Contract: Stake n * 32 ETH + Staking Contract-->>Staking Contract: Load n stored keys and signatures + Staking Contract->>ETH Deposit Contract : Deposit n validators + Staking Contract-->>Staking Contract: Set user as owner of the validator(s) +``` + +### Rewards Withdrawal Flow + +```mermaid +sequenceDiagram + actor U as User Wallet + participant S as Staking Contract + participant R as Recipient Contract + participant D as Dispatcher Contract + participant T as Integrator Treasury + participant T2 as Operator Treasury + + R->>R: Rewards accumulate + + U->>S: Withdraw validator + S-->>R: Deploy if needed + S->>R: Withdraw + R->>D: Split rewards + D->>T: Send commission + D->>T2: Send operator commission + D->>U: Send net rewards +``` + +### Exit Flow + +```mermaid +sequenceDiagram + actor U as User Wallet + participant S as Staking Contract + participant R as Recipient Contract + participant D as Dispatcher Contract + participant T as Integrator Treasury + participant T2 as Operator Treasury + + U->>S: Request exit + S->>S: Emit exit event + + Note over S: Exit event triggers exit message broadcast on CL + Note over R: After the protocol withdrawal process concludes funds are here + + U->>S: Withdraw validator + S-->>R: Deploy if needed + S->>R: Withdraw + R->>D: Split rewards + + D->>T: Send commission + D->>T2: Send operator commission + D->>U: Send principial + net rewards +``` + diff --git a/deployments/goerli_vault/solcInputs/451ca6fca783fd1c389cff7b021a36b9.json b/deployments/goerli_vault/solcInputs/451ca6fca783fd1c389cff7b021a36b9.json new file mode 100644 index 0000000..dabc3e0 --- /dev/null +++ b/deployments/goerli_vault/solcInputs/451ca6fca783fd1c389cff7b021a36b9.json @@ -0,0 +1,76 @@ +{ + "language": "Solidity", + "sources": { + "src/contracts/AuthorizedFeeRecipient.sol": { + "content": "//SPDX-License-Identifier: BUSL-1.1\npragma solidity >=0.8.10;\n\nimport \"./interfaces/IFeeDispatcher.sol\";\nimport \"./libs/DispatchersStorageLib.sol\";\nimport \"./interfaces/IFeeRecipient.sol\";\n\ncontract AuthorizedFeeRecipient is IFeeRecipient {\n /// @notice Constructor replay prevention\n bool internal initialized;\n /// @notice Address where funds are sent to be dispatched\n IFeeDispatcher internal dispatcher;\n /// @notice Public Key root assigned to this receiver\n bytes32 internal publicKeyRoot;\n /// @notice Address of the staking contract\n address internal stakingContract;\n\n error AlreadyInitialized();\n error Unauthorized();\n\n /// @notice Initializes the receiver\n /// @param _dispatcher Address that will handle the fee dispatching\n /// @param _publicKeyRoot Public Key root assigned to this receiver\n function init(address _dispatcher, bytes32 _publicKeyRoot) external {\n if (initialized) {\n revert AlreadyInitialized();\n }\n initialized = true;\n dispatcher = IFeeDispatcher(_dispatcher);\n publicKeyRoot = _publicKeyRoot;\n stakingContract = msg.sender; // The staking contract always calls init\n }\n\n /// @notice Empty calldata fallback\n receive() external payable {}\n\n /// @notice Non-empty calldata fallback\n fallback() external payable {}\n\n /// @notice Triggers a withdrawal by sending its funds + its public key root to the dispatcher\n /// @dev Can be called only be called through the staking contract\n function withdraw() external {\n if (msg.sender != stakingContract) {\n revert Unauthorized();\n }\n dispatcher.dispatch{value: address(this).balance}(publicKeyRoot);\n }\n\n /// @notice Retrieve the assigned public key root\n function getPublicKeyRoot() external view returns (bytes32) {\n return publicKeyRoot;\n }\n\n /// @notice retrieve the assigned withdrawer\n function getWithdrawer() external view returns (address) {\n return dispatcher.getWithdrawer(publicKeyRoot);\n }\n}\n" + }, + "src/contracts/interfaces/IFeeDispatcher.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\ninterface IFeeDispatcher {\n function dispatch(bytes32 _publicKeyRoot) external payable;\n\n function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address);\n}\n" + }, + "src/contracts/libs/DispatchersStorageLib.sol": { + "content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\nlibrary DispatchersStorageLib {\n function getUint256(bytes32 position) internal view returns (uint256 data) {\n assembly {\n data := sload(position)\n }\n }\n\n function setUint256(bytes32 position, uint256 data) internal {\n assembly {\n sstore(position, data)\n }\n }\n\n function getAddress(bytes32 position) internal view returns (address data) {\n assembly {\n data := sload(position)\n }\n }\n\n function setAddress(bytes32 position, address data) internal {\n assembly {\n sstore(position, data)\n }\n }\n}\n" + }, + "src/contracts/interfaces/IFeeRecipient.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\ninterface IFeeRecipient {\n function init(address _dispatcher, bytes32 _publicKeyRoot) external;\n\n function withdraw() external;\n}\n" + }, + "src/contracts/StakingContract.sol": { + "content": "//SPDX-License-Identifier: BUSL-1.1\npragma solidity >=0.8.10;\n\nimport \"./libs/UintLib.sol\";\nimport \"./libs/BytesLib.sol\";\nimport \"./interfaces/IFeeRecipient.sol\";\nimport \"./interfaces/IDepositContract.sol\";\nimport \"./libs/StakingContractStorageLib.sol\";\nimport \"@openzeppelin/contracts/proxy/Clones.sol\";\n\n/// @title Ethereum Staking Contract\n/// @author Kiln\n/// @notice You can use this contract to store validator keys and have users fund them and trigger deposits.\ncontract StakingContract {\n using StakingContractStorageLib for bytes32;\n\n uint256 internal constant EXECUTION_LAYER_SALT_PREFIX = 0;\n uint256 internal constant CONSENSUS_LAYER_SALT_PREFIX = 1;\n uint256 public constant SIGNATURE_LENGTH = 96;\n uint256 public constant PUBLIC_KEY_LENGTH = 48;\n uint256 public constant DEPOSIT_SIZE = 32 ether;\n uint256 internal constant BASIS_POINTS = 10_000;\n\n error Forbidden();\n error InvalidFee();\n error Deactivated();\n error NoOperators();\n error InvalidCall();\n error Unauthorized();\n error DepositFailure();\n error DepositsStopped();\n error InvalidArgument();\n error UnsortedIndexes();\n error InvalidPublicKeys();\n error InvalidSignatures();\n error InvalidWithdrawer();\n error AlreadyInitialized();\n error InvalidDepositValue();\n error NotEnoughValidators();\n error InvalidValidatorCount();\n error DuplicateValidatorKey(bytes);\n error FundedValidatorDeletionAttempt();\n error OperatorLimitTooHigh(uint256 limit, uint256 keyCount);\n error MaximumOperatorCountAlreadyReached();\n\n struct ValidatorAllocationCache {\n bool used;\n uint8 operatorIndex;\n uint32 funded;\n uint32 toDeposit;\n uint32 available;\n }\n\n event Deposit(address indexed caller, address indexed withdrawer, bytes publicKey, bytes signature);\n event ValidatorKeysAdded(uint256 indexed operatorIndex, bytes publicKeys, bytes signatures);\n event ValidatorKeyRemoved(uint256 indexed operatorIndex, bytes publicKey);\n event ChangedWithdrawer(bytes publicKey, address newWithdrawer);\n event ChangedOperatorLimit(uint256 operatorIndex, uint256 limit);\n event ChangedTreasury(address newTreasury);\n event ChangedGlobalFee(uint256 newGlobalFee);\n event ChangedOperatorFee(uint256 newOperatorFee);\n event ChangedAdmin(address newAdmin);\n event ChangedDepositsStopped(bool isStopped);\n event NewOperator(address operatorAddress, address feeRecipientAddress, uint256 index);\n event ChangedOperatorAddresses(uint256 operatorIndex, address operatorAddress, address feeRecipientAddress);\n event DeactivatedOperator(uint256 _operatorIndex);\n event ActivatedOperator(uint256 _operatorIndex);\n event SetWithdrawerCustomizationStatus(bool _status);\n event ExitRequest(address caller, bytes pubkey);\n\n /// @notice Ensures an initialisation call has been called only once per _version value\n /// @param _version The current initialisation value\n modifier init(uint256 _version) {\n if (_version != StakingContractStorageLib.getVersion() + 1) {\n revert AlreadyInitialized();\n }\n\n StakingContractStorageLib.setVersion(_version);\n _;\n }\n\n /// @notice Ensures that the caller is the admin\n modifier onlyAdmin() {\n if (msg.sender != StakingContractStorageLib.getAdmin()) {\n revert Unauthorized();\n }\n\n _;\n }\n\n /// @notice Ensures that the caller is the admin or the operator\n modifier onlyActiveOperatorOrAdmin(uint256 _operatorIndex) {\n if (msg.sender == StakingContractStorageLib.getAdmin()) {\n _;\n } else {\n _onlyActiveOperator(_operatorIndex);\n _;\n }\n }\n\n /// @notice Ensures that the caller is the admin\n modifier onlyActiveOperator(uint256 _operatorIndex) {\n _onlyActiveOperator(_operatorIndex);\n _;\n }\n\n /// @notice Ensures that the caller is the operator fee recipient\n modifier onlyOperatorFeeRecipient(uint256 _operatorIndex) {\n StakingContractStorageLib.OperatorInfo storage operatorInfo = StakingContractStorageLib.getOperators().value[\n _operatorIndex\n ];\n\n if (operatorInfo.deactivated) {\n revert Deactivated();\n }\n\n if (msg.sender != operatorInfo.feeRecipient) {\n revert Unauthorized();\n }\n\n _;\n }\n\n /// @notice Explicit deposit method using msg.sender\n /// @dev A multiple of 32 ETH should be sent\n function deposit() external payable {\n _deposit(msg.sender);\n }\n\n /// @notice Implicit deposit method\n /// @dev A multiple of 32 ETH should be sent\n /// @dev The withdrawer is set to the message sender address\n receive() external payable {\n _deposit(msg.sender);\n }\n\n /// @notice Fallback detection\n /// @dev Fails on any call that fallbacks\n fallback() external payable {\n revert InvalidCall();\n }\n\n function initialize_1(\n address _admin,\n address _treasury,\n address _depositContract,\n address _elDispatcher,\n address _clDispatcher,\n address _feeRecipientImplementation,\n uint256 _globalFee,\n uint256 _operatorFee,\n uint256 globalCommissionLimitBPS,\n uint256 operatorCommissionLimitBPS\n ) external init(1) {\n StakingContractStorageLib.setAdmin(_admin);\n StakingContractStorageLib.setTreasury(_treasury);\n\n if (_globalFee > BASIS_POINTS) {\n revert InvalidFee();\n }\n StakingContractStorageLib.setGlobalFee(_globalFee);\n if (_operatorFee > BASIS_POINTS) {\n revert InvalidFee();\n }\n StakingContractStorageLib.setOperatorFee(_operatorFee);\n\n StakingContractStorageLib.setELDispatcher(_elDispatcher);\n StakingContractStorageLib.setCLDispatcher(_clDispatcher);\n StakingContractStorageLib.setDepositContract(_depositContract);\n StakingContractStorageLib.setFeeRecipientImplementation(_feeRecipientImplementation);\n initialize_2(globalCommissionLimitBPS, operatorCommissionLimitBPS);\n }\n\n function initialize_2(uint256 globalCommissionLimitBPS, uint256 operatorCommissionLimitBPS) public init(2) {\n if (globalCommissionLimitBPS > BASIS_POINTS) {\n revert InvalidFee();\n }\n StakingContractStorageLib.setGlobalCommissionLimit(globalCommissionLimitBPS);\n if (operatorCommissionLimitBPS > BASIS_POINTS) {\n revert InvalidFee();\n }\n StakingContractStorageLib.setOperatorCommissionLimit(operatorCommissionLimitBPS);\n }\n\n /// @notice Changes the behavior of the withdrawer customization logic\n /// @param _enabled True to allow users to customize the withdrawer\n function setWithdrawerCustomizationEnabled(bool _enabled) external onlyAdmin {\n StakingContractStorageLib.setWithdrawerCustomizationEnabled(_enabled);\n emit SetWithdrawerCustomizationStatus(_enabled);\n }\n\n /// @notice Retrieve system admin\n function getAdmin() external view returns (address) {\n return StakingContractStorageLib.getAdmin();\n }\n\n /// @notice Set new treasury\n /// @dev Only callable by admin\n /// @param _newTreasury New Treasury address\n function setTreasury(address _newTreasury) external onlyAdmin {\n emit ChangedTreasury(_newTreasury);\n StakingContractStorageLib.setTreasury(_newTreasury);\n }\n\n /// @notice Retrieve system treasury\n function getTreasury() external view returns (address) {\n return StakingContractStorageLib.getTreasury();\n }\n\n /// @notice Retrieve the global fee\n function getGlobalFee() external view returns (uint256) {\n return StakingContractStorageLib.getGlobalFee();\n }\n\n /// @notice Retrieve the operator fee\n function getOperatorFee() external view returns (uint256) {\n return StakingContractStorageLib.getOperatorFee();\n }\n\n /// @notice Compute the Execution Layer Fee recipient address for a given validator public key\n /// @param _publicKey Validator to get the recipient\n function getELFeeRecipient(bytes calldata _publicKey) external view returns (address) {\n return _getDeterministicReceiver(_publicKey, EXECUTION_LAYER_SALT_PREFIX);\n }\n\n /// @notice Compute the Consensus Layer Fee recipient address for a given validator public key\n /// @param _publicKey Validator to get the recipient\n function getCLFeeRecipient(bytes calldata _publicKey) external view returns (address) {\n return _getDeterministicReceiver(_publicKey, CONSENSUS_LAYER_SALT_PREFIX);\n }\n\n /// @notice Retrieve the Execution & Consensus Layer Fee operator recipient for a given public key\n function getOperatorFeeRecipient(bytes32 pubKeyRoot) external view returns (address) {\n return\n StakingContractStorageLib\n .getOperators()\n .value[StakingContractStorageLib.getOperatorIndexPerValidator().value[pubKeyRoot].operatorIndex]\n .feeRecipient;\n }\n\n /// @notice Retrieve withdrawer of public key\n /// @param _publicKey Public Key to check\n function getWithdrawer(bytes calldata _publicKey) external view returns (address) {\n return _getWithdrawer(_getPubKeyRoot(_publicKey));\n }\n\n /// @notice Retrieve withdrawer of public key root\n /// @param _publicKeyRoot Hash of the public key\n function getWithdrawerFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (address) {\n return _getWithdrawer(_publicKeyRoot);\n }\n\n /// @notice Retrieve whether the validator exit has been requested\n /// @param _publicKeyRoot Public Key Root to check\n function getExitRequestedFromRoot(bytes32 _publicKeyRoot) external view returns (bool) {\n return _getExitRequest(_publicKeyRoot);\n }\n\n /// @notice Return true if the validator already went through the exit logic\n /// @param _publicKeyRoot Public Key Root of the validator\n function getWithdrawnFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (bool) {\n return StakingContractStorageLib.getWithdrawnMap().value[_publicKeyRoot];\n }\n\n /// @notice Allows the CLDispatcher to signal a validator went through the exit logic\n /// @param _publicKeyRoot Public Key Root of the validator\n function toggleWithdrawnFromPublicKeyRoot(bytes32 _publicKeyRoot) external {\n if (msg.sender != StakingContractStorageLib.getCLDispatcher()) {\n revert Unauthorized();\n }\n StakingContractStorageLib.getWithdrawnMap().value[_publicKeyRoot] = true;\n }\n\n /// @notice Returns false if the users can deposit, true if deposits are stopped\n function getDepositsStopped() external view returns (bool) {\n return StakingContractStorageLib.getDepositStopped();\n }\n\n /// @notice Retrieve operator details\n /// @param _operatorIndex Operator index\n function getOperator(uint256 _operatorIndex)\n external\n view\n returns (\n address operatorAddress,\n address feeRecipientAddress,\n uint256 limit,\n uint256 keys,\n uint256 funded,\n uint256 available,\n bool deactivated\n )\n {\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n if (_operatorIndex < operators.value.length) {\n StakingContractStorageLib.ValidatorsFundingInfo memory _operatorInfo = StakingContractStorageLib\n .getValidatorsFundingInfo(_operatorIndex);\n StakingContractStorageLib.OperatorInfo storage _operator = operators.value[_operatorIndex];\n\n (operatorAddress, feeRecipientAddress, limit, keys, deactivated) = (\n _operator.operator,\n _operator.feeRecipient,\n _operator.limit,\n _operator.publicKeys.length,\n _operator.deactivated\n );\n (funded, available) = (_operatorInfo.funded, _operatorInfo.availableKeys);\n }\n }\n\n /// @notice Get details about a validator\n /// @param _operatorIndex Index of the operator running the validator\n /// @param _validatorIndex Index of the validator\n function getValidator(uint256 _operatorIndex, uint256 _validatorIndex)\n external\n view\n returns (\n bytes memory publicKey,\n bytes memory signature,\n address withdrawer,\n bool funded\n )\n {\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n publicKey = operators.value[_operatorIndex].publicKeys[_validatorIndex];\n signature = operators.value[_operatorIndex].signatures[_validatorIndex];\n withdrawer = _getWithdrawer(_getPubKeyRoot(publicKey));\n funded = _validatorIndex < StakingContractStorageLib.getValidatorsFundingInfo(_operatorIndex).funded;\n }\n\n /// @notice Get the total available keys that are ready to be used for deposits\n function getAvailableValidatorCount() external view returns (uint256) {\n return StakingContractStorageLib.getTotalAvailableValidators();\n }\n\n /// @notice Set new admin\n /// @dev Only callable by admin\n /// @param _newAdmin New Administrator address\n function transferOwnership(address _newAdmin) external onlyAdmin {\n StakingContractStorageLib.setPendingAdmin(_newAdmin);\n }\n\n /// @notice New admin must accept its role by calling this method\n /// @dev Only callable by new admin\n function acceptOwnership() external {\n address newAdmin = StakingContractStorageLib.getPendingAdmin();\n\n if (msg.sender != newAdmin) {\n revert Unauthorized();\n }\n StakingContractStorageLib.setAdmin(newAdmin);\n emit ChangedAdmin(newAdmin);\n }\n\n /// @notice Get the new admin's address previously set for an ownership transfer\n function getPendingAdmin() external view returns (address) {\n return StakingContractStorageLib.getPendingAdmin();\n }\n\n /// @notice Add new operator\n /// @dev Only callable by admin\n /// @param _operatorAddress Operator address allowed to add / remove validators\n /// @param _feeRecipientAddress Operator address used to manage rewards\n function addOperator(address _operatorAddress, address _feeRecipientAddress) external onlyAdmin returns (uint256) {\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n StakingContractStorageLib.OperatorInfo memory newOperator;\n\n if (operators.value.length == 251) {\n revert MaximumOperatorCountAlreadyReached();\n }\n newOperator.operator = _operatorAddress;\n newOperator.feeRecipient = _feeRecipientAddress;\n operators.value.push(newOperator);\n uint256 operatorIndex = operators.value.length - 1;\n emit NewOperator(_operatorAddress, _feeRecipientAddress, operatorIndex);\n return operatorIndex;\n }\n\n /// @notice Set new operator addresses (operations and reward management)\n /// @dev Only callable by fee recipient address manager\n /// @param _operatorIndex Index of the operator to update\n /// @param _operatorAddress New operator address for operations management\n /// @param _feeRecipientAddress New operator address for reward management\n function setOperatorAddresses(\n uint256 _operatorIndex,\n address _operatorAddress,\n address _feeRecipientAddress\n ) external onlyOperatorFeeRecipient(_operatorIndex) {\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n\n operators.value[_operatorIndex].operator = _operatorAddress;\n operators.value[_operatorIndex].feeRecipient = _feeRecipientAddress;\n emit ChangedOperatorAddresses(_operatorIndex, _operatorAddress, _feeRecipientAddress);\n }\n\n /// @notice Set withdrawer for public key\n /// @dev Only callable by current public key withdrawer\n /// @param _publicKey Public key to change withdrawer\n /// @param _newWithdrawer New withdrawer address\n function setWithdrawer(bytes calldata _publicKey, address _newWithdrawer) external {\n if (!StakingContractStorageLib.getWithdrawerCustomizationEnabled()) {\n revert Forbidden();\n }\n bytes32 pubkeyRoot = sha256(BytesLib.pad64(_publicKey));\n StakingContractStorageLib.WithdrawersSlot storage withdrawers = StakingContractStorageLib.getWithdrawers();\n\n if (withdrawers.value[pubkeyRoot] != msg.sender) {\n revert Unauthorized();\n }\n\n emit ChangedWithdrawer(_publicKey, _newWithdrawer);\n\n withdrawers.value[pubkeyRoot] = _newWithdrawer;\n }\n\n /// @notice Set operator staking limits\n /// @dev Only callable by admin\n /// @dev Limit should not exceed the validator key count of the operator\n /// @dev Keys should be registered before limit is increased\n /// @dev Allows all keys to be verified by the system admin before limit is increased\n /// @param _operatorIndex Operator Index\n /// @param _limit New staking limit\n function setOperatorLimit(uint256 _operatorIndex, uint256 _limit) external onlyAdmin {\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n if (operators.value[_operatorIndex].deactivated) {\n revert Deactivated();\n }\n uint256 publicKeyCount = operators.value[_operatorIndex].publicKeys.length;\n if (publicKeyCount < _limit) {\n revert OperatorLimitTooHigh(_limit, publicKeyCount);\n }\n operators.value[_operatorIndex].limit = _limit;\n _updateAvailableValidatorCount(_operatorIndex);\n emit ChangedOperatorLimit(_operatorIndex, _limit);\n }\n\n /// @notice Deactivates an operator and changes the fee recipient address and the staking limit\n /// @param _operatorIndex Operator Index\n /// @param _temporaryFeeRecipient Temporary address to receive funds decided by the system admin\n function deactivateOperator(uint256 _operatorIndex, address _temporaryFeeRecipient) external onlyAdmin {\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n operators.value[_operatorIndex].limit = 0;\n emit ChangedOperatorLimit(_operatorIndex, 0);\n operators.value[_operatorIndex].deactivated = true;\n emit DeactivatedOperator(_operatorIndex);\n operators.value[_operatorIndex].feeRecipient = _temporaryFeeRecipient;\n emit ChangedOperatorAddresses(_operatorIndex, operators.value[_operatorIndex].operator, _temporaryFeeRecipient);\n _updateAvailableValidatorCount(_operatorIndex);\n }\n\n /// @notice Activates an operator, without changing its 0 staking limit\n /// @param _operatorIndex Operator Index\n /// @param _newFeeRecipient Sets the fee recipient address\n function activateOperator(uint256 _operatorIndex, address _newFeeRecipient) external onlyAdmin {\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n operators.value[_operatorIndex].deactivated = false;\n emit ActivatedOperator(_operatorIndex);\n operators.value[_operatorIndex].feeRecipient = _newFeeRecipient;\n emit ChangedOperatorAddresses(_operatorIndex, operators.value[_operatorIndex].operator, _newFeeRecipient);\n }\n\n /// @notice Change the Operator fee\n /// @param _operatorFee Fee in Basis Point\n function setOperatorFee(uint256 _operatorFee) external onlyAdmin {\n if (_operatorFee > BASIS_POINTS || _operatorFee > StakingContractStorageLib.getOperatorCommissionLimit()) {\n revert InvalidFee();\n }\n StakingContractStorageLib.setOperatorFee(_operatorFee);\n emit ChangedOperatorFee(_operatorFee);\n }\n\n /// @notice Change the Global fee\n /// @param _globalFee Fee in Basis Point\n function setGlobalFee(uint256 _globalFee) external onlyAdmin {\n if (_globalFee > BASIS_POINTS || _globalFee > StakingContractStorageLib.getGlobalCommissionLimit()) {\n revert InvalidFee();\n }\n StakingContractStorageLib.setGlobalFee(_globalFee);\n emit ChangedGlobalFee(_globalFee);\n }\n\n /// @notice Add new validator public keys and signatures\n /// @dev Only callable by operator\n /// @param _operatorIndex Operator Index\n /// @param _keyCount Number of keys added\n /// @param _publicKeys Concatenated _keyCount public keys\n /// @param _signatures Concatenated _keyCount signatures\n function addValidators(\n uint256 _operatorIndex,\n uint256 _keyCount,\n bytes calldata _publicKeys,\n bytes calldata _signatures\n ) external onlyActiveOperator(_operatorIndex) {\n if (_keyCount == 0) {\n revert InvalidArgument();\n }\n\n if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0 || _publicKeys.length / PUBLIC_KEY_LENGTH != _keyCount) {\n revert InvalidPublicKeys();\n }\n\n if (_signatures.length % SIGNATURE_LENGTH != 0 || _signatures.length / SIGNATURE_LENGTH != _keyCount) {\n revert InvalidSignatures();\n }\n\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n StakingContractStorageLib.OperatorIndexPerValidatorSlot\n storage operatorIndexPerValidator = StakingContractStorageLib.getOperatorIndexPerValidator();\n\n for (uint256 i; i < _keyCount; ) {\n bytes memory publicKey = BytesLib.slice(_publicKeys, i * PUBLIC_KEY_LENGTH, PUBLIC_KEY_LENGTH);\n bytes memory signature = BytesLib.slice(_signatures, i * SIGNATURE_LENGTH, SIGNATURE_LENGTH);\n\n operators.value[_operatorIndex].publicKeys.push(publicKey);\n operators.value[_operatorIndex].signatures.push(signature);\n\n bytes32 pubKeyRoot = _getPubKeyRoot(publicKey);\n\n if (operatorIndexPerValidator.value[pubKeyRoot].enabled) {\n revert DuplicateValidatorKey(publicKey);\n }\n\n operatorIndexPerValidator.value[pubKeyRoot] = StakingContractStorageLib.OperatorIndex({\n enabled: true,\n operatorIndex: uint32(_operatorIndex)\n });\n\n unchecked {\n ++i;\n }\n }\n\n emit ValidatorKeysAdded(_operatorIndex, _publicKeys, _signatures);\n\n _updateAvailableValidatorCount(_operatorIndex);\n }\n\n /// @notice Remove unfunded validators\n /// @dev Only callable by operator\n /// @dev Indexes should be provided in decreasing order\n /// @dev The limit will be set to the lowest removed operator index to ensure all changes above the\n /// lowest removed validator key are verified by the system administrator\n /// @param _operatorIndex Operator Index\n /// @param _indexes List of indexes to delete, in decreasing order\n function removeValidators(uint256 _operatorIndex, uint256[] calldata _indexes)\n external\n onlyActiveOperatorOrAdmin(_operatorIndex)\n {\n if (_indexes.length == 0) {\n revert InvalidArgument();\n }\n\n StakingContractStorageLib.ValidatorsFundingInfo memory operatorInfo = StakingContractStorageLib\n .getValidatorsFundingInfo(_operatorIndex);\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n\n if (_indexes[_indexes.length - 1] < operatorInfo.funded) {\n revert FundedValidatorDeletionAttempt();\n }\n for (uint256 i; i < _indexes.length; ) {\n if (i > 0 && _indexes[i] >= _indexes[i - 1]) {\n revert UnsortedIndexes();\n }\n\n emit ValidatorKeyRemoved(_operatorIndex, operators.value[_operatorIndex].publicKeys[_indexes[i]]);\n if (_indexes[i] == operators.value[_operatorIndex].publicKeys.length - 1) {\n operators.value[_operatorIndex].publicKeys.pop();\n operators.value[_operatorIndex].signatures.pop();\n } else {\n operators.value[_operatorIndex].publicKeys[_indexes[i]] = operators.value[_operatorIndex].publicKeys[\n operators.value[_operatorIndex].publicKeys.length - 1\n ];\n operators.value[_operatorIndex].publicKeys.pop();\n operators.value[_operatorIndex].signatures[_indexes[i]] = operators.value[_operatorIndex].signatures[\n operators.value[_operatorIndex].signatures.length - 1\n ];\n operators.value[_operatorIndex].signatures.pop();\n }\n\n unchecked {\n ++i;\n }\n }\n\n if (_indexes[_indexes.length - 1] < operators.value[_operatorIndex].limit) {\n operators.value[_operatorIndex].limit = _indexes[_indexes.length - 1];\n emit ChangedOperatorLimit(_operatorIndex, _indexes[_indexes.length - 1]);\n }\n\n _updateAvailableValidatorCount(_operatorIndex);\n }\n\n /// @notice Withdraw the Execution Layer Fee for given validators public keys\n /// @dev Funds are sent to the withdrawer account\n /// @dev This method is public on purpose\n /// @param _publicKeys Validators to withdraw Execution Layer Fees from\n function batchWithdrawELFee(bytes calldata _publicKeys) external {\n if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) {\n revert InvalidPublicKeys();\n }\n for (uint256 i = 0; i < _publicKeys.length; ) {\n bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH);\n _onlyWithdrawerOrAdmin(publicKey);\n _deployAndWithdraw(publicKey, EXECUTION_LAYER_SALT_PREFIX, StakingContractStorageLib.getELDispatcher());\n unchecked {\n i += PUBLIC_KEY_LENGTH;\n }\n }\n }\n\n /// @notice Withdraw the Consensus Layer Fee for given validators public keys\n /// @dev Funds are sent to the withdrawer account\n /// @dev This method is public on purpose\n /// @param _publicKeys Validators to withdraw Consensus Layer Fees from\n function batchWithdrawCLFee(bytes calldata _publicKeys) external {\n if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) {\n revert InvalidPublicKeys();\n }\n for (uint256 i = 0; i < _publicKeys.length; ) {\n bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH);\n _onlyWithdrawerOrAdmin(publicKey);\n _deployAndWithdraw(publicKey, CONSENSUS_LAYER_SALT_PREFIX, StakingContractStorageLib.getCLDispatcher());\n unchecked {\n i += PUBLIC_KEY_LENGTH;\n }\n }\n }\n\n /// @notice Withdraw both Consensus and Execution Layer Fees for given validators public keys\n /// @dev Funds are sent to the withdrawer account\n /// @param _publicKeys Validators to withdraw fees from\n function batchWithdraw(bytes calldata _publicKeys) external {\n if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) {\n revert InvalidPublicKeys();\n }\n for (uint256 i = 0; i < _publicKeys.length; ) {\n bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH);\n _onlyWithdrawerOrAdmin(publicKey);\n _deployAndWithdraw(publicKey, EXECUTION_LAYER_SALT_PREFIX, StakingContractStorageLib.getELDispatcher());\n _deployAndWithdraw(publicKey, CONSENSUS_LAYER_SALT_PREFIX, StakingContractStorageLib.getCLDispatcher());\n unchecked {\n i += PUBLIC_KEY_LENGTH;\n }\n }\n }\n\n /// @notice Withdraw the Execution Layer Fee for a given validator public key\n /// @dev Funds are sent to the withdrawer account\n /// @param _publicKey Validator to withdraw Execution Layer Fees from\n function withdrawELFee(bytes calldata _publicKey) external {\n _onlyWithdrawerOrAdmin(_publicKey);\n _deployAndWithdraw(_publicKey, EXECUTION_LAYER_SALT_PREFIX, StakingContractStorageLib.getELDispatcher());\n }\n\n /// @notice Withdraw the Consensus Layer Fee for a given validator public key\n /// @dev Funds are sent to the withdrawer account\n /// @param _publicKey Validator to withdraw Consensus Layer Fees from\n function withdrawCLFee(bytes calldata _publicKey) external {\n _onlyWithdrawerOrAdmin(_publicKey);\n _deployAndWithdraw(_publicKey, CONSENSUS_LAYER_SALT_PREFIX, StakingContractStorageLib.getCLDispatcher());\n }\n\n /// @notice Withdraw both Consensus and Execution Layer Fee for a given validator public key\n /// @dev Reverts if any is null\n /// @param _publicKey Validator to withdraw Execution and Consensus Layer Fees from\n function withdraw(bytes calldata _publicKey) external {\n _onlyWithdrawerOrAdmin(_publicKey);\n _deployAndWithdraw(_publicKey, EXECUTION_LAYER_SALT_PREFIX, StakingContractStorageLib.getELDispatcher());\n _deployAndWithdraw(_publicKey, CONSENSUS_LAYER_SALT_PREFIX, StakingContractStorageLib.getCLDispatcher());\n }\n\n function requestValidatorsExit(bytes calldata _publicKeys) external {\n if (_publicKeys.length % PUBLIC_KEY_LENGTH != 0) {\n revert InvalidPublicKeys();\n }\n for (uint256 i = 0; i < _publicKeys.length; ) {\n bytes memory publicKey = BytesLib.slice(_publicKeys, i, PUBLIC_KEY_LENGTH);\n bytes32 pubKeyRoot = _getPubKeyRoot(publicKey);\n address withdrawer = _getWithdrawer(pubKeyRoot);\n if (msg.sender != withdrawer) {\n revert Unauthorized();\n }\n _setExitRequest(pubKeyRoot, true);\n emit ExitRequest(withdrawer, publicKey);\n unchecked {\n i += PUBLIC_KEY_LENGTH;\n }\n }\n }\n\n /// @notice Utility to stop or allow deposits\n function setDepositsStopped(bool val) external onlyAdmin {\n emit ChangedDepositsStopped(val);\n StakingContractStorageLib.setDepositStopped(val);\n }\n\n /// ██ ███ ██ ████████ ███████ ██████ ███ ██ █████ ██\n /// ██ ████ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██\n /// ██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ███████ ██\n /// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██\n /// ██ ██ ████ ██ ███████ ██ ██ ██ ████ ██ ██ ███████\n\n function _onlyWithdrawerOrAdmin(bytes memory _publicKey) internal view {\n if (\n msg.sender != _getWithdrawer(_getPubKeyRoot(_publicKey)) &&\n StakingContractStorageLib.getAdmin() != msg.sender\n ) {\n revert InvalidWithdrawer();\n }\n }\n\n function _onlyActiveOperator(uint256 _operatorIndex) internal view {\n StakingContractStorageLib.OperatorInfo storage operatorInfo = StakingContractStorageLib.getOperators().value[\n _operatorIndex\n ];\n\n if (operatorInfo.deactivated) {\n revert Deactivated();\n }\n\n if (msg.sender != operatorInfo.operator) {\n revert Unauthorized();\n }\n }\n\n function _getPubKeyRoot(bytes memory _publicKey) internal pure returns (bytes32) {\n return sha256(BytesLib.pad64(_publicKey));\n }\n\n function _getWithdrawer(bytes32 _publicKeyRoot) internal view returns (address) {\n return StakingContractStorageLib.getWithdrawers().value[_publicKeyRoot];\n }\n\n function _getExitRequest(bytes32 _publicKeyRoot) internal view returns (bool) {\n return StakingContractStorageLib.getExitRequestMap().value[_publicKeyRoot];\n }\n\n function _setExitRequest(bytes32 _publicKeyRoot, bool _value) internal {\n StakingContractStorageLib.getExitRequestMap().value[_publicKeyRoot] = _value;\n }\n\n function _updateAvailableValidatorCount(uint256 _operatorIndex) internal {\n StakingContractStorageLib.ValidatorsFundingInfo memory validatorFundingInfo = StakingContractStorageLib\n .getValidatorsFundingInfo(_operatorIndex);\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n\n uint32 oldAvailableCount = validatorFundingInfo.availableKeys;\n uint32 newAvailableCount = 0;\n uint256 cap = _min(operators.value[_operatorIndex].limit, operators.value[_operatorIndex].publicKeys.length);\n\n if (cap <= validatorFundingInfo.funded) {\n StakingContractStorageLib.setValidatorsFundingInfo(_operatorIndex, 0, validatorFundingInfo.funded);\n } else {\n newAvailableCount = uint32(cap - validatorFundingInfo.funded);\n StakingContractStorageLib.setValidatorsFundingInfo(\n _operatorIndex,\n newAvailableCount,\n validatorFundingInfo.funded\n );\n }\n\n if (oldAvailableCount != newAvailableCount) {\n StakingContractStorageLib.setTotalAvailableValidators(\n (StakingContractStorageLib.getTotalAvailableValidators() - oldAvailableCount) + newAvailableCount\n );\n }\n }\n\n function _addressToWithdrawalCredentials(address _recipient) internal pure returns (bytes32) {\n return\n bytes32(uint256(uint160(_recipient)) + 0x0100000000000000000000000000000000000000000000000000000000000000);\n }\n\n function _depositValidatorsOfOperator(\n uint256 _operatorIndex,\n uint256 _validatorCount,\n address _withdrawer\n ) internal {\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n StakingContractStorageLib.OperatorInfo storage operator = operators.value[_operatorIndex];\n StakingContractStorageLib.ValidatorsFundingInfo memory vfi = StakingContractStorageLib.getValidatorsFundingInfo(\n _operatorIndex\n );\n\n for (uint256 i = vfi.funded; i < vfi.funded + _validatorCount; ) {\n bytes memory publicKey = operator.publicKeys[i];\n bytes memory signature = operator.signatures[i];\n address consensusLayerRecipient = _getDeterministicReceiver(publicKey, CONSENSUS_LAYER_SALT_PREFIX);\n bytes32 withdrawalCredentials = _addressToWithdrawalCredentials(consensusLayerRecipient);\n _depositValidator(publicKey, signature, withdrawalCredentials);\n bytes32 pubkeyRoot = _getPubKeyRoot(publicKey);\n StakingContractStorageLib.getWithdrawers().value[pubkeyRoot] = _withdrawer;\n emit Deposit(msg.sender, _withdrawer, publicKey, signature);\n unchecked {\n ++i;\n }\n }\n\n StakingContractStorageLib.setValidatorsFundingInfo(\n _operatorIndex,\n uint32(vfi.availableKeys - _validatorCount),\n uint32(vfi.funded + _validatorCount)\n );\n }\n\n /// @notice Internal utility to deposit a public key, its signature and 32 ETH to the consensus layer\n /// @param _publicKey The Public Key to deposit\n /// @param _signature The Signature to deposit\n /// @param _withdrawalCredentials The Withdrawal Credentials to deposit\n function _depositValidator(\n bytes memory _publicKey,\n bytes memory _signature,\n bytes32 _withdrawalCredentials\n ) internal {\n bytes32 pubkeyRoot = _getPubKeyRoot(_publicKey);\n bytes32 signatureRoot = sha256(\n abi.encodePacked(\n sha256(BytesLib.slice(_signature, 0, 64)),\n sha256(BytesLib.pad64(BytesLib.slice(_signature, 64, SIGNATURE_LENGTH - 64)))\n )\n );\n\n uint256 depositAmount = DEPOSIT_SIZE / 1000000000 wei;\n\n bytes32 depositDataRoot = sha256(\n abi.encodePacked(\n sha256(abi.encodePacked(pubkeyRoot, _withdrawalCredentials)),\n sha256(abi.encodePacked(Uint256Lib.toLittleEndian64(depositAmount), signatureRoot))\n )\n );\n\n uint256 targetBalance = address(this).balance - DEPOSIT_SIZE;\n\n IDepositContract(StakingContractStorageLib.getDepositContract()).deposit{value: DEPOSIT_SIZE}(\n _publicKey,\n abi.encodePacked(_withdrawalCredentials),\n _signature,\n depositDataRoot\n );\n\n if (address(this).balance != targetBalance) {\n revert DepositFailure();\n }\n }\n\n function _depositOnOneOperator(\n address _withdrawer,\n uint256 _depositCount,\n uint256 _totalAvailableValidators\n ) internal {\n _depositValidatorsOfOperator(0, _depositCount, _withdrawer);\n StakingContractStorageLib.setTotalAvailableValidators(_totalAvailableValidators - _depositCount);\n }\n\n function _depositOnTwoOperators(\n address _withdrawer,\n uint256 _depositCount,\n uint256 _totalAvailableValidators\n ) internal {\n StakingContractStorageLib.ValidatorsFundingInfo memory oneOsi = StakingContractStorageLib\n .getValidatorsFundingInfo(0);\n StakingContractStorageLib.ValidatorsFundingInfo memory twoOsi = StakingContractStorageLib\n .getValidatorsFundingInfo(1);\n\n uint256 oneDepositCount;\n uint256 twoDepositCount;\n\n // using this tactic to prevent deposits of 1 validator to always go to operator 2\n if (block.number % 2 == 0) {\n oneDepositCount = _depositCount / 2;\n twoDepositCount = _depositCount - oneDepositCount;\n } else {\n twoDepositCount = _depositCount / 2;\n oneDepositCount = _depositCount - twoDepositCount;\n }\n\n if (oneDepositCount > oneOsi.availableKeys) {\n twoDepositCount = _depositCount - oneOsi.availableKeys;\n oneDepositCount = oneOsi.availableKeys;\n } else if (twoDepositCount > twoOsi.availableKeys) {\n oneDepositCount = _depositCount - twoOsi.availableKeys;\n twoDepositCount = twoOsi.availableKeys;\n }\n\n if (oneDepositCount > 0) {\n _depositValidatorsOfOperator(0, oneDepositCount, _withdrawer);\n }\n if (twoDepositCount > 0) {\n _depositValidatorsOfOperator(1, twoDepositCount, _withdrawer);\n }\n StakingContractStorageLib.setTotalAvailableValidators(\n _totalAvailableValidators - (oneDepositCount + twoDepositCount)\n );\n }\n\n function _getBaseSkip(\n bytes32 blockHash,\n uint256 index,\n uint8 prime\n ) internal pure returns (uint8 base, uint8 skip) {\n base = uint8(blockHash[(index * 2) % 32]) % prime;\n skip = (uint8(blockHash[((index * 2) + 1) % 32]) % (prime - 1)) + 1;\n }\n\n function _getOperatorFundedCount(uint8 operatorIndex, ValidatorAllocationCache[] memory vd)\n internal\n view\n returns (uint32)\n {\n if (operatorIndex >= vd.length) {\n return 0;\n }\n if (vd[operatorIndex].used == false) {\n StakingContractStorageLib.ValidatorsFundingInfo memory osi = StakingContractStorageLib\n .getValidatorsFundingInfo(operatorIndex);\n vd[operatorIndex].used = true;\n vd[operatorIndex].funded = osi.funded;\n vd[operatorIndex].available = osi.availableKeys;\n }\n return vd[operatorIndex].funded + vd[operatorIndex].toDeposit;\n }\n\n function _getOperatorAvailableCount(uint8 operatorIndex, ValidatorAllocationCache[] memory vd)\n internal\n view\n returns (uint32)\n {\n if (operatorIndex >= vd.length) {\n return 0;\n }\n if (vd[operatorIndex].used == false) {\n StakingContractStorageLib.ValidatorsFundingInfo memory osi = StakingContractStorageLib\n .getValidatorsFundingInfo(operatorIndex);\n vd[operatorIndex].used = true;\n vd[operatorIndex].funded = osi.funded;\n vd[operatorIndex].available = osi.availableKeys;\n }\n return vd[operatorIndex].available - vd[operatorIndex].toDeposit;\n }\n\n function _assignTemporaryDeposit(uint8 operatorIndex, ValidatorAllocationCache[] memory vd) internal pure {\n vd[operatorIndex].toDeposit += 1;\n }\n\n function _getBestOperator(\n uint8 alphaIndex,\n uint8 betaIndex,\n bytes32 blockHash,\n ValidatorAllocationCache[] memory vd\n ) internal view returns (uint8) {\n uint256 alphaFundedCount = _getOperatorFundedCount(alphaIndex, vd);\n uint256 betaFundedCount = _getOperatorFundedCount(betaIndex, vd);\n if (alphaFundedCount < betaFundedCount) {\n return alphaIndex;\n } else if (alphaFundedCount > betaFundedCount) {\n return betaIndex;\n } else {\n bool coinToss = (uint8(blockHash[(alphaIndex + betaIndex) % 32]) % 2) == 1;\n if (coinToss == false) {\n return betaIndex;\n } else {\n return alphaIndex;\n }\n }\n }\n\n function _getElligibleOperators(\n uint8 base,\n uint8 skip,\n uint8 prime,\n ValidatorAllocationCache[] memory vd\n ) internal view returns (uint8, uint8) {\n int16 alphaIndex = -1;\n int16 betaIndex = -1;\n uint8 index = base;\n while (alphaIndex == -1 || betaIndex == -1) {\n if (_getOperatorAvailableCount(index, vd) > 0) {\n if (alphaIndex == -1) {\n alphaIndex = int8(index);\n } else {\n betaIndex = int8(index);\n }\n }\n index = uint8((uint256(index) + skip) % prime);\n if (index == base && betaIndex == -1) {\n betaIndex = alphaIndex;\n }\n }\n return (uint8(int8(alphaIndex)), uint8(int8(betaIndex)));\n }\n\n function _depositOnThreeOrMoreOperators(\n address _withdrawer,\n uint256 _depositCount,\n uint256 _totalAvailableValidators,\n StakingContractStorageLib.OperatorsSlot storage _operators\n ) internal {\n uint256 operatorCount = _operators.value.length;\n uint8 optimusPrime = _getClosestPrimeAbove(uint8(operatorCount));\n bytes32 blockHash = blockhash(block.number - 1); // weak random number as it's not a security issue\n\n ValidatorAllocationCache[] memory vd = new ValidatorAllocationCache[](operatorCount);\n\n for (uint256 index; index < _depositCount; ) {\n // Retrieve base index and skip value based on block hash and current loop index\n (uint8 base, uint8 skip) = _getBaseSkip(blockHash, index, optimusPrime);\n // Retrieve two operator indexes pointing to two (or the same) operator(s) that have at least one available\n // validator key to be used for a deposit. This method takes into account possible pending deposits from\n // previous loop rounds.\n (uint8 alphaIndex, uint8 betaIndex) = _getElligibleOperators(base, skip, optimusPrime, vd);\n\n if (alphaIndex == betaIndex) {\n // Assign the deposit to the only operator having available keys\n _assignTemporaryDeposit(alphaIndex, vd);\n } else {\n // Assign the deposit to the operator having the lowest amount of funded keys\n _assignTemporaryDeposit(_getBestOperator(alphaIndex, betaIndex, blockHash, vd), vd);\n }\n\n unchecked {\n ++index;\n }\n }\n\n // Loop through the cached operator values and deposit any pending deposits\n for (uint256 index; index < vd.length; ) {\n if (vd[index].toDeposit > 0) {\n _depositValidatorsOfOperator(index, vd[index].toDeposit, _withdrawer);\n }\n unchecked {\n ++index;\n }\n }\n\n StakingContractStorageLib.setTotalAvailableValidators(_totalAvailableValidators - _depositCount);\n }\n\n function _deposit(address _withdrawer) internal {\n if (StakingContractStorageLib.getDepositStopped()) {\n revert DepositsStopped();\n }\n if (msg.value == 0 || msg.value % DEPOSIT_SIZE != 0) {\n revert InvalidDepositValue();\n }\n uint256 totalAvailableValidators = StakingContractStorageLib.getTotalAvailableValidators();\n uint256 depositCount = msg.value / DEPOSIT_SIZE;\n if (depositCount > totalAvailableValidators) {\n revert NotEnoughValidators();\n }\n StakingContractStorageLib.OperatorsSlot storage operators = StakingContractStorageLib.getOperators();\n if (operators.value.length == 0) {\n revert NoOperators();\n } else if (operators.value.length == 1) {\n _depositOnOneOperator(_withdrawer, depositCount, totalAvailableValidators);\n } else if (operators.value.length == 2) {\n _depositOnTwoOperators(_withdrawer, depositCount, totalAvailableValidators);\n } else {\n _depositOnThreeOrMoreOperators(_withdrawer, depositCount, totalAvailableValidators, operators);\n }\n }\n\n function _primes() internal pure returns (uint8[54] memory primes) {\n primes = [\n 2,\n 3,\n 5,\n 7,\n 11,\n 13,\n 17,\n 19,\n 23,\n 29,\n 31,\n 37,\n 41,\n 43,\n 47,\n 53,\n 59,\n 61,\n 67,\n 71,\n 73,\n 79,\n 83,\n 89,\n 97,\n 101,\n 103,\n 107,\n 109,\n 113,\n 127,\n 131,\n 137,\n 139,\n 149,\n 151,\n 157,\n 163,\n 167,\n 173,\n 179,\n 181,\n 191,\n 193,\n 197,\n 199,\n 211,\n 223,\n 227,\n 229,\n 233,\n 239,\n 241,\n 251\n ];\n }\n\n function _getClosestPrimeAbove(uint8 _count) internal pure returns (uint8) {\n uint8[54] memory primes = _primes();\n for (uint256 i; i < primes.length; ) {\n if (primes[i] >= _count) {\n return primes[i];\n }\n unchecked {\n ++i;\n }\n }\n revert InvalidValidatorCount();\n }\n\n function _min(uint256 _a, uint256 _b) internal pure returns (uint256) {\n if (_a < _b) {\n return _a;\n }\n return _b;\n }\n\n /// @notice Internal utility to compute the receiver deterministic address\n /// @param _publicKey Public Key assigned to the receiver\n /// @param _prefix Prefix used to generate multiple receivers per public key\n function _getDeterministicReceiver(bytes memory _publicKey, uint256 _prefix) internal view returns (address) {\n bytes32 publicKeyRoot = _getPubKeyRoot(_publicKey);\n bytes32 salt = sha256(abi.encodePacked(_prefix, publicKeyRoot));\n address implementation = StakingContractStorageLib.getFeeRecipientImplementation();\n return Clones.predictDeterministicAddress(implementation, salt);\n }\n\n /// @notice Internal utility to deploy and withdraw the fees from a receiver\n /// @param _publicKey Public Key assigned to the receiver\n /// @param _prefix Prefix used to generate multiple receivers per public key\n /// @param _dispatcher Address of the dispatcher contract\n function _deployAndWithdraw(\n bytes memory _publicKey,\n uint256 _prefix,\n address _dispatcher\n ) internal {\n bytes32 publicKeyRoot = _getPubKeyRoot(_publicKey);\n bytes32 feeRecipientSalt = sha256(abi.encodePacked(_prefix, publicKeyRoot));\n address implementation = StakingContractStorageLib.getFeeRecipientImplementation();\n address feeRecipientAddress = Clones.predictDeterministicAddress(implementation, feeRecipientSalt);\n if (feeRecipientAddress.code.length == 0) {\n Clones.cloneDeterministic(implementation, feeRecipientSalt);\n IFeeRecipient(feeRecipientAddress).init(_dispatcher, publicKeyRoot);\n }\n IFeeRecipient(feeRecipientAddress).withdraw();\n }\n}\n" + }, + "src/contracts/libs/UintLib.sol": { + "content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\nlibrary Uint256Lib {\n function toLittleEndian64(uint256 _value) internal pure returns (uint256 result) {\n result = 0;\n uint256 temp_value = _value;\n for (uint256 i = 0; i < 8; ++i) {\n result = (result << 8) | (temp_value & 0xFF);\n temp_value >>= 8;\n }\n\n assert(0 == temp_value); // fully converted\n result <<= (24 * 8);\n }\n}\n" + }, + "src/contracts/libs/BytesLib.sol": { + "content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\nlibrary BytesLib {\n function pad64(bytes memory _b) internal pure returns (bytes memory) {\n assert(_b.length >= 32 && _b.length <= 64);\n if (64 == _b.length) {\n return _b;\n }\n\n bytes memory zero32 = new bytes(32);\n assembly {\n mstore(add(zero32, 0x20), 0)\n }\n\n if (32 == _b.length) {\n return BytesLib.concat(_b, zero32);\n } else {\n return BytesLib.concat(_b, BytesLib.slice(zero32, 0, uint256(64) - _b.length));\n }\n }\n\n function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {\n bytes memory tempBytes;\n\n assembly {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // Store the length of the first bytes array at the beginning of\n // the memory for tempBytes.\n let length := mload(_preBytes)\n mstore(tempBytes, length)\n\n // Maintain a memory counter for the current write location in the\n // temp bytes array by adding the 32 bytes for the array length to\n // the starting location.\n let mc := add(tempBytes, 0x20)\n // Stop copying when the memory counter reaches the length of the\n // first bytes array.\n let end := add(mc, length)\n\n for {\n // Initialize a copy counter to the start of the _preBytes data,\n // 32 bytes into its memory.\n let cc := add(_preBytes, 0x20)\n } lt(mc, end) {\n // Increase both counters by 32 bytes each iteration.\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // Write the _preBytes data into the tempBytes memory 32 bytes\n // at a time.\n mstore(mc, mload(cc))\n }\n\n // Add the length of _postBytes to the current length of tempBytes\n // and store it as the new length in the first 32 bytes of the\n // tempBytes memory.\n length := mload(_postBytes)\n mstore(tempBytes, add(length, mload(tempBytes)))\n\n // Move the memory counter back from a multiple of 0x20 to the\n // actual end of the _preBytes data.\n mc := end\n // Stop copying when the memory counter reaches the new combined\n // length of the arrays.\n end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n // Update the free-memory pointer by padding our last write location\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\n // next 32 byte block, then round down to the nearest multiple of\n // 32. If the sum of the length of the two arrays is zero then add\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\n mstore(\n 0x40,\n and(\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\n not(31) // Round down to the nearest 32 bytes.\n )\n )\n }\n\n return tempBytes;\n }\n\n function slice(\n bytes memory _bytes,\n uint256 _start,\n uint256 _length\n ) internal pure returns (bytes memory) {\n require(_length + 31 >= _length, \"slice_overflow\");\n require(_bytes.length >= _start + _length, \"slice_outOfBounds\");\n\n bytes memory tempBytes;\n\n assembly {\n switch iszero(_length)\n case 0 {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // The first word of the slice result is potentially a partial\n // word read from the original array. To read it, we calculate\n // the length of that partial word and start copying that many\n // bytes into the array. The first word we copy will start with\n // data we don't care about, but the last `lengthmod` bytes will\n // land at the beginning of the contents of the new array. When\n // we're done copying, we overwrite the full first word with\n // the actual length of the slice.\n let lengthmod := and(_length, 31)\n\n // The multiplication in the next line is necessary\n // because when slicing multiples of 32 bytes (lengthmod == 0)\n // the following copy loop was copying the origin's length\n // and then ending prematurely not copying everything it should.\n let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))\n let end := add(mc, _length)\n\n for {\n // The multiplication in the next line has the same exact purpose\n // as the one above.\n let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n mstore(tempBytes, _length)\n\n //update free-memory pointer\n //allocating the array padded to 32 bytes like the compiler does now\n mstore(0x40, and(add(mc, 31), not(31)))\n }\n //if we want a zero-length slice let's just return a zero-length array\n default {\n tempBytes := mload(0x40)\n //zero out the 32 bytes slice we are about to return\n //we need to do it because Solidity does not garbage collect\n mstore(tempBytes, 0)\n\n mstore(0x40, add(tempBytes, 0x20))\n }\n }\n\n return tempBytes;\n }\n}\n" + }, + "src/contracts/interfaces/IDepositContract.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\ninterface IDepositContract {\n function deposit(\n bytes calldata pubkey,\n bytes calldata withdrawalCredentials,\n bytes calldata signature,\n bytes32 depositDataRoot\n ) external payable;\n}\n" + }, + "src/contracts/libs/StakingContractStorageLib.sol": { + "content": "//SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\nlibrary StakingContractStorageLib {\n function getUint256(bytes32 position) internal view returns (uint256 data) {\n assembly {\n data := sload(position)\n }\n }\n\n function setUint256(bytes32 position, uint256 data) internal {\n assembly {\n sstore(position, data)\n }\n }\n\n function getAddress(bytes32 position) internal view returns (address data) {\n assembly {\n data := sload(position)\n }\n }\n\n function setAddress(bytes32 position, address data) internal {\n assembly {\n sstore(position, data)\n }\n }\n\n function getBool(bytes32 position) internal view returns (bool data) {\n assembly {\n data := sload(position)\n }\n }\n\n function setBool(bytes32 position, bool data) internal {\n assembly {\n sstore(position, data)\n }\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant VERSION_SLOT = keccak256(\"StakingContract.version\");\n\n function getVersion() internal view returns (uint256) {\n return getUint256(VERSION_SLOT);\n }\n\n function setVersion(uint256 _newVersion) internal {\n setUint256(VERSION_SLOT, _newVersion);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant ADMIN_SLOT = keccak256(\"StakingContract.admin\");\n bytes32 internal constant PENDING_ADMIN_SLOT = keccak256(\"StakingContract.pendingAdmin\");\n\n function getAdmin() internal view returns (address) {\n return getAddress(ADMIN_SLOT);\n }\n\n function setAdmin(address _newAdmin) internal {\n setAddress(ADMIN_SLOT, _newAdmin);\n }\n\n function getPendingAdmin() internal view returns (address) {\n return getAddress(PENDING_ADMIN_SLOT);\n }\n\n function setPendingAdmin(address _newPendingAdmin) internal {\n setAddress(PENDING_ADMIN_SLOT, _newPendingAdmin);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant TREASURY_SLOT = keccak256(\"StakingContract.treasury\");\n\n function getTreasury() internal view returns (address) {\n return getAddress(TREASURY_SLOT);\n }\n\n function setTreasury(address _newTreasury) internal {\n setAddress(TREASURY_SLOT, _newTreasury);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant DEPOSIT_CONTRACT_SLOT = keccak256(\"StakingContract.depositContract\");\n\n function getDepositContract() internal view returns (address) {\n return getAddress(DEPOSIT_CONTRACT_SLOT);\n }\n\n function setDepositContract(address _newDepositContract) internal {\n setAddress(DEPOSIT_CONTRACT_SLOT, _newDepositContract);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant OPERATORS_SLOT = keccak256(\"StakingContract.operators\");\n\n struct OperatorInfo {\n address operator;\n address feeRecipient;\n uint256 limit;\n bytes[] publicKeys;\n bytes[] signatures;\n bool deactivated;\n }\n\n struct OperatorsSlot {\n OperatorInfo[] value;\n }\n\n function getOperators() internal pure returns (OperatorsSlot storage p) {\n bytes32 slot = OPERATORS_SLOT;\n assembly {\n p.slot := slot\n }\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant VALIDATORS_FUNDING_INFO_SLOT = keccak256(\"StakingContract.validatorsFundingInfo\");\n\n struct ValidatorsFundingInfo {\n uint32 availableKeys;\n uint32 funded;\n }\n\n struct UintToUintMappingSlot {\n mapping(uint256 => uint256) value;\n }\n\n function getValidatorsFundingInfo(uint256 _index) internal view returns (ValidatorsFundingInfo memory vfi) {\n UintToUintMappingSlot storage p;\n bytes32 slot = VALIDATORS_FUNDING_INFO_SLOT;\n\n assembly {\n p.slot := slot\n }\n\n uint256 slotIndex = _index >> 2;\n uint256 innerIndex = (_index & 3) << 6;\n uint256 value = p.value[slotIndex] >> innerIndex;\n vfi.availableKeys = uint32(value);\n vfi.funded = uint32(value >> 32);\n }\n\n function setValidatorsFundingInfo(\n uint256 _index,\n uint32 _availableKeys,\n uint32 _funded\n ) internal {\n UintToUintMappingSlot storage p;\n bytes32 slot = VALIDATORS_FUNDING_INFO_SLOT;\n\n assembly {\n p.slot := slot\n }\n\n uint256 slotIndex = _index >> 2;\n uint256 innerIndex = (_index & 3) << 6;\n p.value[slotIndex] =\n (p.value[slotIndex] & (~(uint256(0xFFFFFFFFFFFFFFFF) << innerIndex))) |\n ((uint256(_availableKeys) | (uint256(_funded) << 32)) << innerIndex);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant TOTAL_AVAILABLE_VALIDATORS_SLOT = keccak256(\"StakingContract.totalAvailableValidators\");\n\n function getTotalAvailableValidators() internal view returns (uint256) {\n return getUint256(TOTAL_AVAILABLE_VALIDATORS_SLOT);\n }\n\n function setTotalAvailableValidators(uint256 _newTotal) internal {\n setUint256(TOTAL_AVAILABLE_VALIDATORS_SLOT, _newTotal);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant WITHDRAWERS_SLOT = keccak256(\"StakingContract.withdrawers\");\n\n struct WithdrawersSlot {\n mapping(bytes32 => address) value;\n }\n\n function getWithdrawers() internal pure returns (WithdrawersSlot storage p) {\n bytes32 slot = WITHDRAWERS_SLOT;\n assembly {\n p.slot := slot\n }\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n struct OperatorIndex {\n bool enabled;\n uint32 operatorIndex;\n }\n\n struct OperatorIndexPerValidatorSlot {\n mapping(bytes32 => OperatorIndex) value;\n }\n\n bytes32 internal constant OPERATOR_INDEX_PER_VALIDATOR_SLOT =\n keccak256(\"StakingContract.operatorIndexPerValidator\");\n\n function getOperatorIndexPerValidator() internal pure returns (OperatorIndexPerValidatorSlot storage p) {\n bytes32 slot = OPERATOR_INDEX_PER_VALIDATOR_SLOT;\n assembly {\n p.slot := slot\n }\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant GLOBAL_FEE_SLOT = keccak256(\"StakingContract.globalFee\");\n\n function getGlobalFee() internal view returns (uint256) {\n return getUint256(GLOBAL_FEE_SLOT);\n }\n\n function setGlobalFee(uint256 _newTreasuryFee) internal {\n setUint256(GLOBAL_FEE_SLOT, _newTreasuryFee);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant OPERATOR_FEE_SLOT = keccak256(\"StakingContract.operatorFee\");\n\n function getOperatorFee() internal view returns (uint256) {\n return getUint256(OPERATOR_FEE_SLOT);\n }\n\n function setOperatorFee(uint256 _newOperatorFee) internal {\n setUint256(OPERATOR_FEE_SLOT, _newOperatorFee);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant EL_DISPATCHER_SLOT = keccak256(\"StakingContract.executionLayerDispatcher\");\n\n function getELDispatcher() internal view returns (address) {\n return getAddress(EL_DISPATCHER_SLOT);\n }\n\n function setELDispatcher(address _newElDispatcher) internal {\n setAddress(EL_DISPATCHER_SLOT, _newElDispatcher);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant CL_DISPATCHER_SLOT = keccak256(\"StakingContract.consensusLayerDispatcher\");\n\n function getCLDispatcher() internal view returns (address) {\n return getAddress(CL_DISPATCHER_SLOT);\n }\n\n function setCLDispatcher(address _newClDispatcher) internal {\n setAddress(CL_DISPATCHER_SLOT, _newClDispatcher);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant FEE_RECIPIENT_IMPLEMENTATION_SLOT =\n keccak256(\"StakingContract.feeRecipientImplementation\");\n\n function getFeeRecipientImplementation() internal view returns (address) {\n return getAddress(FEE_RECIPIENT_IMPLEMENTATION_SLOT);\n }\n\n function setFeeRecipientImplementation(address _newFeeRecipientImplementation) internal {\n setAddress(FEE_RECIPIENT_IMPLEMENTATION_SLOT, _newFeeRecipientImplementation);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant WITHDRAWER_CUSTOMIZATION_ENABLED_SLOT =\n keccak256(\"StakingContract.withdrawerCustomizationEnabled\");\n\n function getWithdrawerCustomizationEnabled() internal view returns (bool) {\n return getBool(WITHDRAWER_CUSTOMIZATION_ENABLED_SLOT);\n }\n\n function setWithdrawerCustomizationEnabled(bool _enabled) internal {\n setBool(WITHDRAWER_CUSTOMIZATION_ENABLED_SLOT, _enabled);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant EXIT_REQUEST_MAPPING_SLOT = keccak256(\"StakingContract.exitRequest\");\n\n struct ExitRequestMap {\n mapping(bytes32 => bool) value;\n }\n\n function getExitRequestMap() internal pure returns (ExitRequestMap storage p) {\n bytes32 slot = EXIT_REQUEST_MAPPING_SLOT;\n assembly {\n p.slot := slot\n }\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant WITHDRAWN_MAPPING_SLOT = keccak256(\"StakingContract.withdrawn\");\n\n struct WithdrawnMap {\n mapping(bytes32 => bool) value;\n }\n\n function getWithdrawnMap() internal pure returns (WithdrawnMap storage p) {\n bytes32 slot = WITHDRAWN_MAPPING_SLOT;\n assembly {\n p.slot := slot\n }\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant GLOBAL_COMMISSION_LIMIT_SLOT = keccak256(\"StakingContract.globalCommissionLimit\");\n\n function getGlobalCommissionLimit() internal view returns (uint256) {\n return getUint256(GLOBAL_COMMISSION_LIMIT_SLOT);\n }\n\n function setGlobalCommissionLimit(uint256 value) internal {\n setUint256(GLOBAL_COMMISSION_LIMIT_SLOT, value);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant OPERATOR_COMMISSION_LIMIT_SLOT = keccak256(\"StakingContract.operatorCommissionLimit\");\n\n function getOperatorCommissionLimit() internal view returns (uint256) {\n return getUint256(OPERATOR_COMMISSION_LIMIT_SLOT);\n }\n\n function setOperatorCommissionLimit(uint256 value) internal {\n setUint256(OPERATOR_COMMISSION_LIMIT_SLOT, value);\n }\n\n /* ========================================\n ===========================================\n =========================================*/\n\n bytes32 internal constant DEPOSIT_STOPPED_SLOT = keccak256(\"StakingContract.depositStopped\");\n\n function getDepositStopped() internal view returns (bool) {\n return getBool(DEPOSIT_STOPPED_SLOT);\n }\n\n function setDepositStopped(bool val) internal {\n setBool(DEPOSIT_STOPPED_SLOT, val);\n }\n}\n" + }, + "@openzeppelin/contracts/proxy/Clones.sol": { + "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (proxy/Clones.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for\n * deploying minimal proxy contracts, also known as \"clones\".\n *\n * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies\n * > a minimal bytecode implementation that delegates all calls to a known, fixed address.\n *\n * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`\n * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the\n * deterministic method.\n *\n * _Available since v3.4._\n */\nlibrary Clones {\n /**\n * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.\n *\n * This function uses the create opcode, which should never revert.\n */\n function clone(address implementation) internal returns (address instance) {\n assembly {\n let ptr := mload(0x40)\n mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)\n mstore(add(ptr, 0x14), shl(0x60, implementation))\n mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)\n instance := create(0, ptr, 0x37)\n }\n require(instance != address(0), \"ERC1167: create failed\");\n }\n\n /**\n * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.\n *\n * This function uses the create2 opcode and a `salt` to deterministically deploy\n * the clone. Using the same `implementation` and `salt` multiple time will revert, since\n * the clones cannot be deployed twice at the same address.\n */\n function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {\n assembly {\n let ptr := mload(0x40)\n mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)\n mstore(add(ptr, 0x14), shl(0x60, implementation))\n mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)\n instance := create2(0, ptr, 0x37, salt)\n }\n require(instance != address(0), \"ERC1167: create2 failed\");\n }\n\n /**\n * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.\n */\n function predictDeterministicAddress(\n address implementation,\n bytes32 salt,\n address deployer\n ) internal pure returns (address predicted) {\n assembly {\n let ptr := mload(0x40)\n mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)\n mstore(add(ptr, 0x14), shl(0x60, implementation))\n mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf3ff00000000000000000000000000000000)\n mstore(add(ptr, 0x38), shl(0x60, deployer))\n mstore(add(ptr, 0x4c), salt)\n mstore(add(ptr, 0x6c), keccak256(ptr, 0x37))\n predicted := keccak256(add(ptr, 0x37), 0x55)\n }\n }\n\n /**\n * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.\n */\n function predictDeterministicAddress(address implementation, bytes32 salt)\n internal\n view\n returns (address predicted)\n {\n return predictDeterministicAddress(implementation, salt, address(this));\n }\n}\n" + }, + "src/contracts/ExecutionLayerFeeDispatcher.sol": { + "content": "//SPDX-License-Identifier: BUSL-1.1\npragma solidity >=0.8.10;\n\nimport \"./libs/DispatchersStorageLib.sol\";\nimport \"./interfaces/IStakingContractFeeDetails.sol\";\nimport \"./interfaces/IFeeDispatcher.sol\";\n\n/// @title Execution Layer Fee Recipient\n/// @author Kiln\n/// @notice This contract can be used to receive fees from a validator and split them with a node operator\ncontract ExecutionLayerFeeDispatcher is IFeeDispatcher {\n using DispatchersStorageLib for bytes32;\n\n event Withdrawal(\n address indexed withdrawer,\n address indexed feeRecipient,\n bytes32 pubKeyRoot,\n uint256 rewards,\n uint256 nodeOperatorFee,\n uint256 treasuryFee\n );\n\n error TreasuryReceiveError(bytes errorData);\n error FeeRecipientReceiveError(bytes errorData);\n error WithdrawerReceiveError(bytes errorData);\n error ZeroBalanceWithdrawal();\n error AlreadyInitialized();\n error InvalidCall();\n\n bytes32 internal constant STAKING_CONTRACT_ADDRESS_SLOT =\n keccak256(\"ExecutionLayerFeeRecipient.stakingContractAddress\");\n uint256 internal constant BASIS_POINTS = 10_000;\n bytes32 internal constant VERSION_SLOT = keccak256(\"ExecutionLayerFeeRecipient.version\");\n\n /// @notice Ensures an initialisation call has been called only once per _version value\n /// @param _version The current initialisation value\n modifier init(uint256 _version) {\n if (_version != VERSION_SLOT.getUint256() + 1) {\n revert AlreadyInitialized();\n }\n\n VERSION_SLOT.setUint256(_version);\n\n _;\n }\n\n /// @notice Constructor method allowing us to prevent calls to initCLFR by setting the appropriate version\n constructor(uint256 _version) {\n VERSION_SLOT.setUint256(_version);\n }\n\n /// @notice Initialize the contract by storing the staking contract and the public key in storage\n /// @param _stakingContract Address of the Staking Contract\n function initELD(address _stakingContract) external init(1) {\n STAKING_CONTRACT_ADDRESS_SLOT.setAddress(_stakingContract);\n }\n\n /// @notice Performs a withdrawal on this contract's balance\n function dispatch(bytes32 _publicKeyRoot) external payable {\n uint256 balance = address(this).balance;\n if (balance == 0) {\n revert ZeroBalanceWithdrawal();\n }\n IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails(\n STAKING_CONTRACT_ADDRESS_SLOT.getAddress()\n );\n address withdrawer = stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot);\n address operator = stakingContract.getOperatorFeeRecipient(_publicKeyRoot);\n address treasury = stakingContract.getTreasury();\n uint256 globalFee = (balance * stakingContract.getGlobalFee()) / BASIS_POINTS;\n uint256 operatorFee = (globalFee * stakingContract.getOperatorFee()) / BASIS_POINTS;\n\n (bool status, bytes memory data) = withdrawer.call{value: balance - globalFee}(\"\");\n if (status == false) {\n revert WithdrawerReceiveError(data);\n }\n if (globalFee > 0) {\n (status, data) = treasury.call{value: globalFee - operatorFee}(\"\");\n if (status == false) {\n revert FeeRecipientReceiveError(data);\n }\n }\n if (operatorFee > 0) {\n (status, data) = operator.call{value: operatorFee}(\"\");\n if (status == false) {\n revert TreasuryReceiveError(data);\n }\n }\n emit Withdrawal(\n withdrawer,\n operator,\n _publicKeyRoot,\n balance - globalFee,\n operatorFee,\n globalFee - operatorFee\n );\n }\n\n /// @notice Retrieve the staking contract address\n function getStakingContract() external view returns (address) {\n return STAKING_CONTRACT_ADDRESS_SLOT.getAddress();\n }\n\n /// @notice Retrieve the assigned withdrawer for the given public key root\n /// @param _publicKeyRoot Public key root to get the owner\n function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address) {\n IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails(\n STAKING_CONTRACT_ADDRESS_SLOT.getAddress()\n );\n return stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot);\n }\n\n receive() external payable {\n revert InvalidCall();\n }\n\n fallback() external payable {\n revert InvalidCall();\n }\n}\n" + }, + "src/contracts/interfaces/IStakingContractFeeDetails.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity >=0.8.10;\n\ninterface IStakingContractFeeDetails {\n function getWithdrawerFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (address);\n\n function getLastWithdrawFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (uint256);\n\n function getTreasury() external view returns (address);\n\n function getOperatorFeeRecipient(bytes32 pubKeyRoot) external view returns (address);\n\n function getGlobalFee() external view returns (uint256);\n\n function getOperatorFee() external view returns (uint256);\n\n function getExitRequestedFromRoot(bytes32 _publicKeyRoot) external view returns (bool);\n\n function getWithdrawnFromPublicKeyRoot(bytes32 _publicKeyRoot) external view returns (bool);\n\n function toggleWithdrawnFromPublicKeyRoot(bytes32 _publicKeyRoot) external;\n}\n" + }, + "src/contracts/FeeRecipient.sol": { + "content": "//SPDX-License-Identifier: BUSL-1.1\npragma solidity >=0.8.10;\n\nimport \"./interfaces/IFeeDispatcher.sol\";\n\ncontract FeeRecipient {\n /// @notice Constructor replay prevention\n bool internal initialized;\n /// @notice Address where funds are sent to be dispatched\n IFeeDispatcher internal dispatcher;\n /// @notice Public Key root assigned to this receiver\n bytes32 internal publicKeyRoot;\n\n error AlreadyInitialized();\n\n /// @notice Initializes the receiver\n /// @param _dispatcher Address that will handle the fee dispatching\n /// @param _publicKeyRoot Public Key root assigned to this receiver\n function init(address _dispatcher, bytes32 _publicKeyRoot) external {\n if (initialized) {\n revert AlreadyInitialized();\n }\n initialized = true;\n dispatcher = IFeeDispatcher(_dispatcher);\n publicKeyRoot = _publicKeyRoot;\n }\n\n /// @notice Empty calldata fallback\n receive() external payable {}\n\n /// @notice Non-empty calldata fallback\n fallback() external payable {}\n\n /// @notice Triggers a withdrawal by sending its funds + its public key root to the dispatcher\n /// @dev Can be called by any wallet as recipients are not parameters\n function withdraw() external {\n dispatcher.dispatch{value: address(this).balance}(publicKeyRoot);\n }\n\n /// @notice Retrieve the assigned public key root\n function getPublicKeyRoot() external view returns (bytes32) {\n return publicKeyRoot;\n }\n\n /// @notice retrieve the assigned withdrawer\n function getWithdrawer() external view returns (address) {\n return dispatcher.getWithdrawer(publicKeyRoot);\n }\n}\n" + }, + "src/contracts/ConsensusLayerFeeDispatcher.sol": { + "content": "//SPDX-License-Identifier: BUSL-1.1\npragma solidity >=0.8.10;\n\nimport \"./libs/DispatchersStorageLib.sol\";\nimport \"./interfaces/IStakingContractFeeDetails.sol\";\nimport \"./interfaces/IFeeDispatcher.sol\";\n\n/// @title Consensus Layer Fee Recipient\n/// @author Kiln\n/// @notice This contract can be used to receive fees from a validator and split them with a node operator\ncontract ConsensusLayerFeeDispatcher is IFeeDispatcher {\n using DispatchersStorageLib for bytes32;\n\n event Withdrawal(\n address indexed withdrawer,\n address indexed feeRecipient,\n bytes32 pubKeyRoot,\n uint256 rewards,\n uint256 nodeOperatorFee,\n uint256 treasuryFee\n );\n\n error TreasuryReceiveError(bytes errorData);\n error FeeRecipientReceiveError(bytes errorData);\n error WithdrawerReceiveError(bytes errorData);\n error ZeroBalanceWithdrawal();\n error AlreadyInitialized();\n error InvalidCall();\n error ValidatorNotMigrated();\n\n bytes32 internal constant STAKING_CONTRACT_ADDRESS_SLOT =\n keccak256(\"ConsensusLayerFeeRecipient.stakingContractAddress\");\n uint256 internal constant BASIS_POINTS = 10_000;\n bytes32 internal constant VERSION_SLOT = keccak256(\"ConsensusLayerFeeRecipient.version\");\n\n /// @notice Ensures an initialisation call has been called only once per _version value\n /// @param _version The current initialisation value\n modifier init(uint256 _version) {\n if (_version != VERSION_SLOT.getUint256() + 1) {\n revert AlreadyInitialized();\n }\n\n VERSION_SLOT.setUint256(_version);\n\n _;\n }\n\n /// @notice Constructor method allowing us to prevent calls to initCLFR by setting the appropriate version\n constructor(uint256 _version) {\n VERSION_SLOT.setUint256(_version);\n }\n\n /// @notice Initialize the contract by storing the staking contract\n /// @param _stakingContract Address of the Staking Contract\n function initCLD(address _stakingContract) external init(1) {\n STAKING_CONTRACT_ADDRESS_SLOT.setAddress(_stakingContract);\n }\n\n /// @notice Performs a withdrawal on this contract's balance\n function dispatch(bytes32 _publicKeyRoot) external payable {\n IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails(\n STAKING_CONTRACT_ADDRESS_SLOT.getAddress()\n );\n\n uint256 balance = address(this).balance; // this has taken into account msg.value\n if (balance == 0) {\n revert ZeroBalanceWithdrawal();\n }\n\n bool exitRequested = stakingContract.getExitRequestedFromRoot(_publicKeyRoot);\n bool withdrawn = stakingContract.getWithdrawnFromPublicKeyRoot(_publicKeyRoot);\n\n uint256 nonExemptBalance = balance;\n\n if (exitRequested && balance >= 32 ether && !withdrawn) {\n // In case of healthy exit, exempt 32 ETH once from the balance, happens only once.\n // !withdrawn prevents this logic being reused to not pay the fee on rewards\n nonExemptBalance -= 32 ether;\n stakingContract.toggleWithdrawnFromPublicKeyRoot(_publicKeyRoot);\n }\n // if balance is less than 32 ether we don't exempt anything\n // in case of slashing (withdrawn balance < 32 ETH), staker will be rebated manually\n // It will compensate for the commission taken on its principal and the loss according to\n // the SLA described in the Terms&Conditions\n\n uint256 globalFee = (nonExemptBalance * stakingContract.getGlobalFee()) / BASIS_POINTS;\n\n uint256 operatorFee = (globalFee * stakingContract.getOperatorFee()) / BASIS_POINTS;\n\n address withdrawer = stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot);\n (bool status, bytes memory data) = withdrawer.call{value: balance - globalFee}(\"\");\n if (status == false) {\n revert WithdrawerReceiveError(data);\n }\n address operator = stakingContract.getOperatorFeeRecipient(_publicKeyRoot);\n if (globalFee > 0) {\n address treasury = stakingContract.getTreasury();\n (status, data) = treasury.call{value: globalFee - operatorFee}(\"\");\n if (status == false) {\n revert TreasuryReceiveError(data);\n }\n\n (status, data) = operator.call{value: operatorFee}(\"\");\n if (status == false) {\n revert FeeRecipientReceiveError(data);\n }\n }\n emit Withdrawal(\n withdrawer,\n operator,\n _publicKeyRoot,\n balance - globalFee,\n operatorFee,\n globalFee - operatorFee\n );\n }\n\n /// @notice Retrieve the staking contract address\n function getStakingContract() external view returns (address) {\n return STAKING_CONTRACT_ADDRESS_SLOT.getAddress();\n }\n\n /// @notice Retrieve the assigned withdrawer for the given public key root\n /// @param _publicKeyRoot Public key root to get the owner\n function getWithdrawer(bytes32 _publicKeyRoot) external view returns (address) {\n IStakingContractFeeDetails stakingContract = IStakingContractFeeDetails(\n STAKING_CONTRACT_ADDRESS_SLOT.getAddress()\n );\n return stakingContract.getWithdrawerFromPublicKeyRoot(_publicKeyRoot);\n }\n\n receive() external payable {\n revert InvalidCall();\n }\n\n fallback() external payable {\n revert InvalidCall();\n }\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.gasEstimates", + "devdoc", + "userdoc" + ], + "": [ + "ast" + ] + } + }, + "metadata": { + "useLiteralContent": true + } + } +} \ No newline at end of file diff --git a/deployments/mainnet_live/stakingContractPostUpgrade.abi.json b/deployments/mainnet_live/stakingContractPostUpgrade.abi.json new file mode 100644 index 0000000..5518218 --- /dev/null +++ b/deployments/mainnet_live/stakingContractPostUpgrade.abi.json @@ -0,0 +1,1430 @@ +[ + { + "inputs": [], + "name": "AlreadyInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "Deactivated", + "type": "error" + }, + { + "inputs": [], + "name": "DepositFailure", + "type": "error" + }, + { + "inputs": [], + "name": "DepositsStopped", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "DuplicateValidatorKey", + "type": "error" + }, + { + "inputs": [], + "name": "Forbidden", + "type": "error" + }, + { + "inputs": [], + "name": "FundedValidatorDeletionAttempt", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidArgument", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDepositValue", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidFee", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPublicKeys", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignatures", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidValidatorCount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidWithdrawer", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "LastEditAfterSnapshot", + "type": "error" + }, + { + "inputs": [], + "name": "MaximumOperatorCountAlreadyReached", + "type": "error" + }, + { + "inputs": [], + "name": "NoOperators", + "type": "error" + }, + { + "inputs": [], + "name": "NotEnoughValidators", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "keyCount", + "type": "uint256" + } + ], + "name": "OperatorLimitTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "PublicKeyNotInContract", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + }, + { + "inputs": [], + "name": "UnsortedIndexes", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + } + ], + "name": "ActivatedOperator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "ChangedAdmin", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isStopped", + "type": "bool" + } + ], + "name": "ChangedDepositsStopped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newGlobalFee", + "type": "uint256" + } + ], + "name": "ChangedGlobalFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "operatorIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "operatorAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "feeRecipientAddress", + "type": "address" + } + ], + "name": "ChangedOperatorAddresses", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newOperatorFee", + "type": "uint256" + } + ], + "name": "ChangedOperatorFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "operatorIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "ChangedOperatorLimit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newTreasury", + "type": "address" + } + ], + "name": "ChangedTreasury", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "publicKey", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "address", + "name": "newWithdrawer", + "type": "address" + } + ], + "name": "ChangedWithdrawer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + } + ], + "name": "DeactivatedOperator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "withdrawer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "publicKey", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "pubkey", + "type": "bytes" + } + ], + "name": "ExitRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operatorAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "feeRecipientAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "NewOperator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "SetWithdrawerCustomizationStatus", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "operatorIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "publicKey", + "type": "bytes" + } + ], + "name": "ValidatorKeyRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "operatorIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "publicKeys", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + } + ], + "name": "ValidatorKeysAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "ValidatorsEdited", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "DEPOSIT_SIZE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PUBLIC_KEY_LENGTH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SIGNATURE_LENGTH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_newFeeRecipient", + "type": "address" + } + ], + "name": "activateOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_operatorAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_feeRecipientAddress", + "type": "address" + } + ], + "name": "addOperator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_keyCount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_publicKeys", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "_signatures", + "type": "bytes" + } + ], + "name": "addValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKeys", + "type": "bytes" + } + ], + "name": "batchWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKeys", + "type": "bytes" + } + ], + "name": "batchWithdrawCLFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKeys", + "type": "bytes" + } + ], + "name": "batchWithdrawELFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_temporaryFeeRecipient", + "type": "address" + } + ], + "name": "deactivateOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAvailableValidatorCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKey", + "type": "bytes" + } + ], + "name": "getCLFeeRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDepositsStopped", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKey", + "type": "bytes" + } + ], + "name": "getELFeeRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_publicKeyRoot", + "type": "bytes32" + } + ], + "name": "getEnabledFromPublicKeyRoot", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_publicKeyRoot", + "type": "bytes32" + } + ], + "name": "getExitRequestedFromRoot", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getGlobalFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + } + ], + "name": "getOperator", + "outputs": [ + { + "internalType": "address", + "name": "operatorAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRecipientAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "keys", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "funded", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "deactivated", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOperatorFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "pubKeyRoot", + "type": "bytes32" + } + ], + "name": "getOperatorFeeRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPendingAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTreasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_validatorIndex", + "type": "uint256" + } + ], + "name": "getValidator", + "outputs": [ + { + "internalType": "bytes", + "name": "publicKey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "address", + "name": "withdrawer", + "type": "address" + }, + { + "internalType": "bool", + "name": "funded", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKey", + "type": "bytes" + } + ], + "name": "getWithdrawer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_publicKeyRoot", + "type": "bytes32" + } + ], + "name": "getWithdrawerFromPublicKeyRoot", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_publicKeyRoot", + "type": "bytes32" + } + ], + "name": "getWithdrawnFromPublicKeyRoot", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "address", + "name": "_treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "_depositContract", + "type": "address" + }, + { + "internalType": "address", + "name": "_elDispatcher", + "type": "address" + }, + { + "internalType": "address", + "name": "_clDispatcher", + "type": "address" + }, + { + "internalType": "address", + "name": "_feeRecipientImplementation", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_globalFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_operatorFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "globalCommissionLimitBPS", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "operatorCommissionLimitBPS", + "type": "uint256" + } + ], + "name": "initialize_1", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "globalCommissionLimitBPS", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "operatorCommissionLimitBPS", + "type": "uint256" + } + ], + "name": "initialize_2", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "_indexes", + "type": "uint256[]" + } + ], + "name": "removeValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKeys", + "type": "bytes" + } + ], + "name": "requestValidatorsExit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "val", + "type": "bool" + } + ], + "name": "setDepositsStopped", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_globalFee", + "type": "uint256" + } + ], + "name": "setGlobalFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_operatorAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_feeRecipientAddress", + "type": "address" + } + ], + "name": "setOperatorAddresses", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorFee", + "type": "uint256" + } + ], + "name": "setOperatorFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_operatorIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_snapshot", + "type": "uint256" + } + ], + "name": "setOperatorLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newTreasury", + "type": "address" + } + ], + "name": "setTreasury", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKey", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_newWithdrawer", + "type": "address" + } + ], + "name": "setWithdrawer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_enabled", + "type": "bool" + } + ], + "name": "setWithdrawerCustomizationEnabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_publicKeyRoot", + "type": "bytes32" + } + ], + "name": "toggleWithdrawnFromPublicKeyRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newAdmin", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKey", + "type": "bytes" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKey", + "type": "bytes" + } + ], + "name": "withdrawCLFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "_publicKey", + "type": "bytes" + } + ], + "name": "withdrawELFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CallWhenPaused", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } + ] \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index ba89a71..3b0b901 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,5 +6,6 @@ cache_path = 'forge-cache' solc = '0.8.13' optimizer = true optimizer_runs = 200 +gas_limit = "18446744073709551615" # u64::MAX # See more config options https://github.com/gakonst/foundry/tree/master/config \ No newline at end of file diff --git a/utils/get_recipients_and_balances.py b/utils/get_recipients_and_balances.py new file mode 100644 index 0000000..c66a079 --- /dev/null +++ b/utils/get_recipients_and_balances.py @@ -0,0 +1,62 @@ +import sys +import os +import json +import requests +from web3 import Web3 +import time + +stakingContractAbi = '[{"inputs":[],"name":"CallWhenPaused","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"admin_","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"changeAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"implementation_","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"name":"upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"Deactivated","type":"error"},{"inputs":[],"name":"DepositFailure","type":"error"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"}],"name":"DuplicateValidatorKey","type":"error"},{"inputs":[],"name":"Forbidden","type":"error"},{"inputs":[],"name":"FundedValidatorDeletionAttempt","type":"error"},{"inputs":[],"name":"InvalidArgument","type":"error"},{"inputs":[],"name":"InvalidCall","type":"error"},{"inputs":[],"name":"InvalidDepositValue","type":"error"},{"inputs":[],"name":"InvalidFee","type":"error"},{"inputs":[],"name":"InvalidPublicKeys","type":"error"},{"inputs":[],"name":"InvalidSignatures","type":"error"},{"inputs":[],"name":"InvalidValidatorCount","type":"error"},{"inputs":[],"name":"MaximumOperatorCountAlreadyReached","type":"error"},{"inputs":[],"name":"NoOperators","type":"error"},{"inputs":[],"name":"NotEnoughValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"limit","type":"uint256"},{"internalType":"uint256","name":"keyCount","type":"uint256"}],"name":"OperatorLimitTooHigh","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnsortedIndexes","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_operatorIndex","type":"uint256"}],"name":"ActivatedOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"ChangedAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newGlobalFee","type":"uint256"}],"name":"ChangedGlobalFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"operatorIndex","type":"uint256"},{"indexed":false,"internalType":"address","name":"operatorAddress","type":"address"},{"indexed":false,"internalType":"address","name":"feeRecipientAddress","type":"address"}],"name":"ChangedOperatorAddresses","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newOperatorFee","type":"uint256"}],"name":"ChangedOperatorFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"operatorIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"limit","type":"uint256"}],"name":"ChangedOperatorLimit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newTreasury","type":"address"}],"name":"ChangedTreasury","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"publicKey","type":"bytes"},{"indexed":false,"internalType":"address","name":"newWithdrawer","type":"address"}],"name":"ChangedWithdrawer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_operatorIndex","type":"uint256"}],"name":"DeactivatedOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"withdrawer","type":"address"},{"indexed":false,"internalType":"bytes","name":"publicKey","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"signature","type":"bytes"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"operatorAddress","type":"address"},{"indexed":false,"internalType":"address","name":"feeRecipientAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"}],"name":"NewOperator","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"_status","type":"bool"}],"name":"SetWithdrawerCustomizationStatus","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"operatorIndex","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"publicKey","type":"bytes"}],"name":"ValidatorKeyRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"operatorIndex","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"publicKeys","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"signatures","type":"bytes"}],"name":"ValidatorKeysAdded","type":"event"},{"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PUBLIC_KEY_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorIndex","type":"uint256"},{"internalType":"address","name":"_newFeeRecipient","type":"address"}],"name":"activateOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_operatorAddress","type":"address"},{"internalType":"address","name":"_feeRecipientAddress","type":"address"}],"name":"addOperator","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorIndex","type":"uint256"},{"internalType":"uint256","name":"_keyCount","type":"uint256"},{"internalType":"bytes","name":"_publicKeys","type":"bytes"},{"internalType":"bytes","name":"_signatures","type":"bytes"}],"name":"addValidators","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorIndex","type":"uint256"},{"internalType":"address","name":"_temporaryFeeRecipient","type":"address"}],"name":"deactivateOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAvailableValidatorCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_publicKey","type":"bytes"}],"name":"getCLFeeRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_publicKey","type":"bytes"}],"name":"getELFeeRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getGlobalFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorIndex","type":"uint256"}],"name":"getOperator","outputs":[{"internalType":"address","name":"operatorAddress","type":"address"},{"internalType":"address","name":"feeRecipientAddress","type":"address"},{"internalType":"uint256","name":"limit","type":"uint256"},{"internalType":"uint256","name":"keys","type":"uint256"},{"internalType":"uint256","name":"funded","type":"uint256"},{"internalType":"uint256","name":"available","type":"uint256"},{"internalType":"bool","name":"deactivated","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOperatorFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"pubKeyRoot","type":"bytes32"}],"name":"getOperatorFeeRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPendingAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTreasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorIndex","type":"uint256"},{"internalType":"uint256","name":"_validatorIndex","type":"uint256"}],"name":"getValidator","outputs":[{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"address","name":"withdrawer","type":"address"},{"internalType":"bool","name":"funded","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_publicKey","type":"bytes"}],"name":"getWithdrawer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_publicKeyRoot","type":"bytes32"}],"name":"getWithdrawerFromPublicKeyRoot","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_treasury","type":"address"},{"internalType":"address","name":"_depositContract","type":"address"},{"internalType":"address","name":"_elDispatcher","type":"address"},{"internalType":"address","name":"_clDispatcher","type":"address"},{"internalType":"address","name":"_feeRecipientImplementation","type":"address"},{"internalType":"uint256","name":"_globalFee","type":"uint256"},{"internalType":"uint256","name":"_operatorFee","type":"uint256"}],"name":"initialize_1","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorIndex","type":"uint256"},{"internalType":"uint256[]","name":"_indexes","type":"uint256[]"}],"name":"removeValidators","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_globalFee","type":"uint256"}],"name":"setGlobalFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorIndex","type":"uint256"},{"internalType":"address","name":"_operatorAddress","type":"address"},{"internalType":"address","name":"_feeRecipientAddress","type":"address"}],"name":"setOperatorAddresses","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorFee","type":"uint256"}],"name":"setOperatorFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_operatorIndex","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"setOperatorLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newTreasury","type":"address"}],"name":"setTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_publicKey","type":"bytes"},{"internalType":"address","name":"_newWithdrawer","type":"address"}],"name":"setWithdrawer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_enabled","type":"bool"}],"name":"setWithdrawerCustomizationEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAdmin","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_publicKey","type":"bytes"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_publicKey","type":"bytes"}],"name":"withdrawCLFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_publicKey","type":"bytes"}],"name":"withdrawELFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_logic","type":"address"},{"internalType":"address","name":"admin_","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"payable","type":"constructor"}]' + +ETHERSCAN_API_KEY = os.environ.get("ETHERSCAN_API_KEY") +ETHEREUM_RPC_URL = os.environ.get("RPCA") + +validators = {} + +def main(): + if len(sys.argv) != 2: + print("Usage: python get_unrealized_revenue.py ") + sys.exit(1) + + timestamp = int(sys.argv[1]) + print("\n\n") + print("Getting block number for timestamp:", timestamp) + block_number = get_block_number(timestamp) + print("Block number:", block_number) + + w3 = Web3(Web3.HTTPProvider(ETHEREUM_RPC_URL)) + + print("timestamp of block:", w3.eth.get_block(block_number)["timestamp"]) + print("") + + stakingContract = w3.eth.contract(address="0x1e68238cE926DEC62b3FBC99AB06eB1D85CE0270", abi=stakingContractAbi) + + funded = stakingContract.caller(block_identifier=block_number).getOperator(0) + funded = funded[4] + + start = time.time() + for i in range(0, funded): + if i != 0 and i % 25 == 0: + print (f'Getting validator: {i} of {funded} = {i * 100 / funded:.2f} % // time elapsed: {time.time() - start:.2f} seconds // time left {(time.time() - start) * (funded - i) / i:.2f} seconds') + + validator = stakingContract.caller(block_identifier=block_number).getValidator(0, i) + cl = stakingContract.caller(block_identifier=block_number).getCLFeeRecipient(validator[0]) + el = stakingContract.caller(block_identifier=block_number).getELFeeRecipient(validator[0]) + cl_balance = w3.eth.get_balance(cl, block_identifier=block_number) + el_balance = w3.eth.get_balance(el, block_identifier=block_number) + validators[validator[0]] = {"cl":cl , "el":el, "cl_balance":cl_balance, "el_balance":el_balance} + + # dump the validators to csv + with open("validators.csv", "w") as f: + f.write("public_key,cl,el,cl_balance,el_balance\n") + for key in validators: + f.write("0x{},{},{},{},{}\n".format(key.hex(), validators[key]["cl"], validators[key]["el"], validators[key]["cl_balance"], validators[key]["el_balance"])) + +def get_block_number(timestamp): + url = "https://api.etherscan.io/api?module=block&action=getblocknobytime×tamp={}&closest=before&apikey={}".format(timestamp, ETHERSCAN_API_KEY) + response = requests.get(url) + data = json.loads(response.text) + block_number = data["result"].strip() + return int(block_number) + +if __name__ == "__main__": + main() diff --git a/utils/get_unrealized_revenue.py b/utils/get_unrealized_revenue.py new file mode 100644 index 0000000..b5e57a7 --- /dev/null +++ b/utils/get_unrealized_revenue.py @@ -0,0 +1,32 @@ +FEE = 0.08 +KILN_SHARE = 0.375 + +import csv + +def main(): + with open('validators.csv', 'r') as file: + reader = csv.DictReader(file) + cl_total = 0 + el_total = 0 + for row in reader: + cl_rewards = int(row['cl_balance']) + if cl_rewards > 31e18: + #print(f'CL rewards: {cl_rewards/1e18}') + exemption = min(cl_rewards, 32e18) + cl_rewards -= exemption + print(f'Rewards after correction: {cl_rewards/1e18}') + + cl_total += cl_rewards + + el_total += int(row['el_balance']) + + print(f'CL rewards: {cl_total/1e18}') + print(f'EL rewards: {el_total/1e18}') + total_rewards = cl_total + el_total + print(f'Total rewards: {(total_rewards)/1e18}') + print(f'Unrealized revenue: {total_rewards/1e18 * FEE}') + print(f'Unrealized revenue owed to Kiln: {total_rewards/1e18 * FEE * KILN_SHARE}') + +if __name__ == "__main__": + main() +