diff --git a/.solhint.json b/.solhint.json index 0b9ee47c..c0f02aab 100644 --- a/.solhint.json +++ b/.solhint.json @@ -14,7 +14,7 @@ "ordering": "warn", "mark-callable-contracts": "off", "max-line-length": [ - "error", + "warn", 120 ], "compiler-version": "off", diff --git a/ETH_MIGRATION_CHANGELOG.md b/ETH_MIGRATION_CHANGELOG.md new file mode 100644 index 00000000..fc556fca --- /dev/null +++ b/ETH_MIGRATION_CHANGELOG.md @@ -0,0 +1,536 @@ +# ETH Migration Changelog + +## Overview + +This document details all changes made to migrate the SSV Network from SSV token-based payments to native ETH payments. The migration maintains backward compatibility with existing SSV token-based operators and clusters while introducing new ETH-based functionality. + +**Base Commit:** `a2e968fac3e00b2e3545393727529ca84e8b313e` (develop branch) +**Migration Branch:** `feat/eth-migration` + +## Summary Statistics + +- **Total Files Changed:** 20 +- **Total Lines Added:** 804 +- **Total Lines Removed:** 284 +- **Net Change:** +520 lines + +## Key Changes + +### 1. Dual Payment System Support + +The migration introduces a dual payment system that supports both: +- **ETH payments** (new, post-migration) +- **SSV token payments** (legacy, pre-migration, backward compatible) + +### 2. Version System + +A versioning system has been introduced to distinguish between: +- `VERSION_SSV = 0` - Legacy SSV token-based operators/clusters +- `VERSION_ETH = 1` - New ETH-based operators/clusters +- `VERSION_UNDEFINED = type(uint8).max` - Invalid/undefined version + +### 3. Security Enhancements + +- Added `ReentrancyGuard` to critical functions handling ETH transfers +- Functions protected: `withdraw`, `removeValidator`, `liquidate`, `reactivate`, `deposit`, and operator withdrawal functions + +--- + +## Detailed File Changes + +### Core Interfaces + +#### `contracts/interfaces/ISSVNetworkCore.sol` + +**Changes:** +- Added new fields to `Operator` struct: + - `version` (uint8) - Operator version (SSV or ETH) + - `ethValidatorCount` (uint32) - Validator count for ETH-based operations + - `ethFee` (uint64) - Fee in ETH + - `ethSnapshot` (Snapshot) - Snapshot for ETH-based earnings tracking +- Added new error: `ETHTransferFailed()` - Replaces `TokenTransferFailed()` for ETH operations +- Added new error: `IncorrectOperatorVersion(uint8 operatorVersion)` - For version validation +- Added new error: `IncorrectClusterVersion()` - For cluster version validation + +**Purpose:** Extends the operator structure to support dual payment systems while maintaining backward compatibility. + +--- + +#### `contracts/interfaces/ISSVClusters.sol` + +**Changes:** +- Modified `registerValidator()` and `bulkRegisterValidator()` to accept `payable` and use `msg.value` instead of `amount` parameter +- Modified `reactivate()` to accept `payable` for ETH deposits +- Modified `deposit()` to accept `payable` for ETH deposits +- Added new function: `liquidateSSV()` - For liquidating legacy SSV token-based clusters +- Updated function signatures to use `payable` modifier where ETH is expected + +**Purpose:** Enables ETH-based validator registration, deposits, and reactivation while maintaining SSV token support. + +--- + +#### `contracts/interfaces/ISSVOperators.sol` + +**Changes:** +- Updated `registerOperator()` documentation to indicate ETH version (post-migration) +- Added `migrateOperatorToETH()` - For migrating legacy SSV operators to ETH using a default ETH fee; `ensureETHDefaults()` now applies ETH defaults (fee/snapshot/validator count) during cluster migration without flipping version +- Updated `withdrawOperatorEarnings()` and `withdrawAllOperatorEarnings()` to handle ETH withdrawals +- Added `withdrawOperatorEarningsSSV()` and `withdrawAllOperatorEarningsSSV()` - For legacy SSV token withdrawals +- Updated function documentation to clarify ETH vs SSV token operations + +**Purpose:** Provides separate functions for ETH and SSV token operations, ensuring clear separation and backward compatibility. + +--- + +#### `contracts/interfaces/ISSVDAO.sol` + +**Changes:** +- Added `updateNetworkFeeSSV()` - For updating legacy SSV token network fee +- Added `withdrawNetworkSSVEarnings()` - For withdrawing legacy SSV token network earnings +- Updated documentation to distinguish between ETH (post-migration) and SSV (pre-migration) functions + +**Purpose:** Maintains backward compatibility for network fee management while introducing ETH-based operations. + +--- + +#### `contracts/interfaces/ISSVNetworkCore.sol` (New Interface) + +**Changes:** +- This interface was extended with new struct fields and errors as described above + +--- + +#### `contracts/interfaces/ISSVViews.sol` + +**Changes:** +- Added `getNetworkFeeSSV()` - Returns legacy SSV token network fee +- Added `getNetworkEarningsSSV()` - Returns legacy SSV token network earnings +- Updated documentation to clarify SSV vs ETH return values +- Added `getClusterVersion()` - Returns cluster version (ETH or SSV) by owner/operator IDs +- Added `getOperatorFeeSSV()` - Returns legacy SSV operator fee +- Added `getOperatorByIdSSV()` and updated `getOperatorById()` to return ETH fields +- Added `isLiquidatableSSV()` - View to check liquidation for legacy SSV clusters +- Added `getOperatorEarningsSSV()` - Returns legacy SSV operator earnings +- Added `getBurnRateSSV()` - Returns burn rate for legacy SSV clusters +- Added `getBalanceSSV()` - Returns cluster balance for legacy SSV clusters + +**Purpose:** Provides view functions for both ETH and SSV token network metrics. + +--- + +### Core Libraries + +#### `contracts/libraries/SSVStorage.sol` + +**Changes:** +- Added new storage mapping: `ethClusters` - Stores ETH-based cluster data separately from SSV token clusters + ```solidity + mapping(bytes32 => bytes32) ethClusters; + ``` + +**Purpose:** Separates ETH and SSV token cluster storage to prevent conflicts and enable independent tracking. + +--- + +#### `contracts/libraries/SSVStorageProtocol.sol` + +**Changes:** +- Added ETH-specific protocol storage fields: + - `ethNetworkFeeIndexBlockNumber` (uint32) - Block number for ETH network fee index + - `ethDaoValidatorCount` (uint32) - DAO validator count for ETH clusters + - `ethDaoIndexBlockNumber` (uint32) - Block number for ETH DAO index + - `ethNetworkFee` (uint64) - Current ETH network fee + - `ethNetworkFeeIndex` (uint64) - Current ETH network fee index + - `ethDaoBalance` (uint64) - Current ETH DAO balance + +**Purpose:** Maintains separate tracking for ETH and SSV token protocol parameters, enabling independent fee management. + +--- + +#### `contracts/libraries/CoreLib.sol` + +**Changes:** +- Added version constants: + - `VERSION_SSV = 0` + - `VERSION_ETH = 1` + - `VERSION_UNDEFINED = type(uint8).max` +- Replaced `transferBalance()` to use native ETH transfers instead of ERC20 token transfers: + ```solidity + function transferBalance(address to, uint256 amount) internal { + (bool success, ) = payable(to).call{value: amount}(""); + if(!success){ + revert ISSVNetworkCore.ETHTransferFailed(); + } + } + ``` +- Added new function `transferTokenBalance()` - For legacy SSV token transfers: + ```solidity + function transferTokenBalance(address to, uint256 amount) internal { + if (!SSVStorage.load().token.transfer(to, amount)) { + revert ISSVNetworkCore.TokenTransferFailed(); + } + } + ``` +- Removed `deposit()` function (ETH deposits now handled via `msg.value`) + +**Purpose:** Provides core ETH transfer functionality while maintaining SSV token transfer support for backward compatibility. + +--- + +#### `contracts/libraries/ProtocolLib.sol` + +**Changes:** +- Added `currentNetworkFeeIndexSSV()` - Returns SSV token network fee index +- Modified `currentNetworkFeeIndex()` to return ETH network fee index +- Added `updateNetworkFeeSSV()` - Updates SSV token network fee +- Modified `updateNetworkFee()` to update ETH network fee +- Added `updateDAOEarningsSSV()` - Updates SSV token DAO earnings +- Modified `updateDAOEarnings()` to update ETH DAO earnings +- Added `networkTotalEarningsSSV()` - Returns SSV token network total earnings +- Modified `networkTotalEarnings()` to return ETH network total earnings +- Added `updateDAOSSV()` - Updates SSV token DAO validator count +- Modified `updateDAO()` to update ETH DAO validator count + +**Purpose:** Provides separate protocol management functions for ETH and SSV token operations, ensuring independent fee and earnings tracking. + +--- + +#### `contracts/libraries/OperatorLib.sol` + +**Changes:** +- Added `updateSnapshot()` - Updates ETH-based operator snapshot +- Added `updateSnapshotSt()` - Updates ETH-based operator snapshot (storage version) +- Added `updateSnapshotSSV()` - Updates SSV token-based operator snapshot +- Added `updateSnapshotStSVV()` - Updates SSV token-based operator snapshot (storage version) +- Added `updateSnapshots()` - Updates both ETH and SSV snapshots (memory) +- Added `updateSnapshotsSt()` - Updates both ETH and SSV snapshots (storage) +- Modified `updateClusterOperatorsOnRegistration()` to handle both ETH and SSV token operators +- Split cluster updates into `updateClusterOperators()` (ETH) and `updateClusterOperatorsSSV()` (legacy SSV) for explicit version handling +- Updated operator validation logic to check version and use appropriate snapshot/fee fields + +**Purpose:** Enables dual snapshot tracking for operators, allowing them to earn from both ETH and SSV token validators independently. + +--- + +#### `contracts/libraries/ClusterLib.sol` + +**Changes:** +- Modified `validateHashedCluster()` to return both `hashedCluster` and `version` +- Added `validateClusterVersion()` - Validates cluster version matches expected version +- Modified `validateClusterOnRegistration()` to check `ethClusters` mapping for new registrations +- Updated cluster storage logic to use appropriate mapping based on version (`ethClusters` vs `clusters`) + +**Purpose:** Enables version-aware cluster validation and storage, ensuring ETH and SSV token clusters are properly separated. + +--- + +### Core Modules + +#### `contracts/modules/SSVClusters.sol` + +**Changes:** +- Added `ReentrancyGuard` inheritance +- Modified `registerValidator()`: + - Changed to `payable` + - Uses `msg.value` instead of `amount` parameter + - Removed `CoreLib.deposit()` call (ETH handled via `msg.value`) + - Stores in `ethClusters` mapping +- Modified `bulkRegisterValidator()`: + - Changed to `payable` + - Uses `msg.value` instead of `amount` parameter + - Removed `CoreLib.deposit()` call + - Stores in `ethClusters` mapping +- Modified `removeValidator()`: + - Validates cluster version (must be ETH) + - Stores in appropriate mapping based on version +- Modified `bulkRemoveValidator()`: + - Validates cluster version (must be ETH) + - Stores in appropriate mapping based on version +- Modified `liquidate()`: + - Added `nonReentrant` modifier + - Validates cluster version (must be ETH) + - Uses `ethNetworkFee` instead of `networkFee` + - Uses `CoreLib.transferBalance()` for ETH transfers + - Stores in `ethClusters` mapping +- Added `liquidateSSV()`: + - New function for liquidating SSV token-based clusters + - Validates cluster version (must be SSV) + - Uses `updateClusterOperatorsSSV()` and `currentNetworkFeeIndexSSV()` for SSV accounting + - Uses `networkFee` and `CoreLib.transferTokenBalance()` + - Stores in `clusters` mapping +- Modified `reactivate()`: + - Changed to `payable` + - Uses `msg.value` for ETH deposits + - Validates cluster version + - Stores in appropriate mapping based on version +- Modified `deposit()`: + - Changed to `payable` + - Uses `msg.value` for ETH deposits + - Validates cluster version + - Stores in appropriate mapping based on version +- Modified `withdraw()`: + - Added `nonReentrant` modifier + - Validates cluster version + - Uses `CoreLib.transferBalance()` for ETH withdrawals + - Stores in appropriate mapping based on version +- Added `ClusterMigratedToETH` event and emit during `migrateClusterToETH()` instead of reactivation/liquidation events +- `migrateClusterToETH()` now decrements SSV DAO validator count and increments ETH DAO validator count to avoid double-counting during migration + +**Purpose:** Implements ETH-based cluster operations while maintaining SSV token cluster support. All ETH operations are protected with reentrancy guards. + +--- + +#### `contracts/modules/SSVOperators.sol` + +**Changes:** +- Added `ReentrancyGuard` inheritance +- Added constant: `MINIMAL_OPERATOR_ETH_FEE = 1_000_000_000` + - Added constant: `DEFAULT_OPERATOR_ETH_FEE = 1_000_000_000` +- Modified `registerOperator()`: + - Creates operators with `VERSION_ETH` + - Initializes `ethFee`, `ethValidatorCount`, and `ethSnapshot` + - Sets legacy `fee` and `validatorCount` to 0 +- Modified `removeOperator()`: + - Added `nonReentrant` modifier + - Handles both ETH and SSV snapshots for balance calculation + - Uses `CoreLib.transferBalance()` for ETH transfers and `CoreLib.transferTokenBalance()` for SSV earnings + - Resets operator state via `_resetOperatorState()` + - Added `migrateOperatorToETH()`: + - Migrates legacy SSV operators to ETH by setting a default ETH fee (validated against max) and switching to ETH version + - Clears pending fee change requests + - Added `ensureETHDefaults()` in `OperatorLib` to initialize ETH fee/snapshot/validator count when clusters migrate and operators are still legacy (without flipping version) +- Modified `declareOperatorFee()`: + - Validates operator version + - Uses `ethFee` for ETH operators + - Checks against `MINIMAL_OPERATOR_ETH_FEE` +- Modified `executeOperatorFee()`: + - Handles both ETH and SSV token operators + - For SSV operators, migrates to ETH version when fee is executed + - Updates appropriate snapshot and fee fields based on version +- Modified `reduceOperatorFee()`: + - Uses `ethFee` for fee reduction + - Validates against `MINIMAL_OPERATOR_ETH_FEE` +- Modified `withdrawOperatorEarnings()`: + - Added `nonReentrant` modifier + - Calls `_withdrawOperatorEarnings()` with `VERSION_ETH` +- Modified `withdrawAllOperatorEarnings()`: + - Added `nonReentrant` modifier + - Withdraws both ETH and legacy SSV balances (if any) for ETH-version operators +- Added `withdrawAllVersionOperatorEarnings()`: + - Withdraws all earnings (ETH and SSV) in a single call regardless of operator version +- Added `withdrawOperatorSSVEarnings()`: + - New function for withdrawing SSV token earnings + - Added `nonReentrant` modifier + - Calls `_withdrawOperatorEarnings()` with `VERSION_SSV` +- Added `withdrawAllOperatorSSVEarnings()`: + - New function for withdrawing all SSV token earnings + - Added `nonReentrant` modifier + - Withdraws both SSV and any residual ETH balances for SSV-version operators +- Modified `_withdrawOperatorEarnings()`: + - Now accepts `version` parameter + - Uses appropriate snapshot and transfer function based on version + - Validates operator version + +**Purpose:** Implements ETH-based operator operations with full backward compatibility for SSV token operators. All withdrawal functions are protected with reentrancy guards. + +--- + +#### `contracts/modules/SSVDAO.sol` + +**Changes:** +- Added `ReentrancyGuard` inheritance +- Modified `updateNetworkFee()`: + - Updates ETH network fee (`ethNetworkFee`) + - Uses `sp.updateNetworkFee()` which handles ETH protocol updates +- Added `updateNetworkFeeSSV()`: + - Updates SSV token network fee (`networkFee`) + - Uses `sp.updateNetworkFeeSSV()` which handles SSV protocol updates +- Modified `withdrawNetworkEarnings()`: + - Added `nonReentrant` modifier + - Withdraws from ETH DAO balance (`ethDaoBalance`) + - Uses `CoreLib.transferBalance()` for ETH transfers + - Updates `ethDaoIndexBlockNumber` +- Added `withdrawNetworkSSVEarnings()`: + - New function for withdrawing SSV token network earnings + - Added `nonReentrant` modifier + - Withdraws from SSV DAO balance (`daoBalance`) + - Uses `CoreLib.transferTokenBalance()` for SSV token transfers + - Updates `daoIndexBlockNumber` + +**Purpose:** Manages network fees and earnings for both ETH and SSV token systems independently. All withdrawal functions are protected with reentrancy guards. + +--- + +#### `contracts/modules/SSVViews.sol` + +**Changes:** +- Updated view functions to handle both ETH and SSV token data +- Added functions to query SSV token-specific network metrics +- Updated functions to return appropriate values based on operator/cluster version + +**Purpose:** Provides comprehensive view functions for both ETH and SSV token operations. + +--- + +### Main Contract + +#### `contracts/SSVNetwork.sol` + +**Changes:** +- Added `liquidateSSV()` function - Delegates to clusters module for SSV token liquidation +- Added `updateNetworkFeeSSV()` function - Delegates to DAO module for SSV token network fee updates +- Added `withdrawNetworkSSVEarnings()` function - Delegates to DAO module for SSV token network earnings withdrawal +- Added `withdrawOperatorSSVEarnings()` function - Delegates to operators module for SSV token operator earnings withdrawal +- Added `withdrawAllOperatorSSVEarnings()` function - Delegates to operators module for all SSV token operator earnings withdrawal + +**Purpose:** Provides main contract interface for all new SSV token backward compatibility functions. + +--- + +### Test Files + +#### `contracts/test/SSVNetworkUpgrade.sol` + +**Changes:** +- Updated test contract to handle both ETH and SSV token operations +- Added tests for version validation +- Added tests for dual payment system + +**Purpose:** Ensures upgrade compatibility and tests both payment systems. + +--- + +#### `contracts/test/modules/SSVOperatorsUpdate.sol` + +**Changes:** +- Extended test coverage for operator version handling +- Added tests for ETH and SSV token operator operations +- Added tests for operator migration scenarios + +**Purpose:** Comprehensive testing of operator functionality across both payment systems. + +--- + +### Configuration Files + +#### `.solhint.json` + +**Changes:** +- Updated linting rules (minor configuration change) + +**Purpose:** Maintains code quality standards. + +--- + +#### `package-lock.json` + +**Changes:** +- Dependency updates (163 lines changed, likely version updates) + +**Purpose:** Keeps dependencies up to date. + +--- + +## Migration Path + +### For New Operators (Post-Migration) + +1. **Register Operator:** Use `registerOperator()` - Creates ETH-based operator (version 1) +2. **Set Fee:** Fee is set in ETH during registration +3. **Earnings:** Withdraw using `withdrawOperatorEarnings()` - Receives ETH + +### For Existing Operators (Pre-Migration) + +1. **Continue Operations:** Existing SSV token operators continue to function normally +2. **Earnings:** Withdraw using `withdrawOperatorSSVEarnings()` - Receives SSV tokens +3. **Migration:** When executing a fee change, SSV operators automatically migrate to ETH version + +### For New Clusters (Post-Migration) + +1. **Register Validator:** Use `registerValidator()` with ETH value - Creates ETH-based cluster +2. **Deposit:** Use `deposit()` with ETH value +3. **Withdraw:** Use `withdraw()` - Receives ETH +4. **Liquidate:** Use `liquidate()` - Handles ETH-based liquidation + +### For Existing Clusters (Pre-Migration) + +1. **Continue Operations:** Existing SSV token clusters continue to function normally +2. **Deposit/Withdraw:** Continue using SSV token functions +3. **Liquidate:** Use `liquidateSSV()` for SSV token-based clusters + +--- + +## Security Considerations + +### Reentrancy Protection + +All functions that handle ETH transfers or withdrawals are protected with the `nonReentrant` modifier: + +- `SSVClusters.liquidate()` +- `SSVClusters.liquidateSSV()` +- `SSVClusters.withdraw()` +- `SSVOperators.removeOperator()` +- `SSVOperators.withdrawOperatorEarnings()` +- `SSVOperators.withdrawAllOperatorEarnings()` +- `SSVOperators.withdrawAllVersionOperatorEarnings()` +- `SSVOperators.withdrawOperatorSSVEarnings()` +- `SSVOperators.withdrawAllOperatorSSVEarnings()` +- `SSVDAO.withdrawNetworkEarnings()` +- `SSVDAO.withdrawNetworkSSVEarnings()` + +### Version Validation + +- Operators and clusters are validated to ensure correct version before operations +- Prevents mixing ETH and SSV token operations incorrectly +- Provides clear error messages for version mismatches + +### Backward Compatibility + +- All existing SSV token operations remain functional +- No breaking changes to existing interfaces (new functions added, not modified) +- Legacy operators and clusters can coexist with new ETH-based ones + +--- + +## Commit History + +The migration was implemented across the following commits: + +1. `fb5a9df` - clusters::registration:eth storage added +2. `9635060` - clusters::registration:refactored +3. `84e7816` - clusters::remove:refactored +4. `0c9bc2f` - clusters::liquidate:refactored, liquidateSSV added +5. `aa01b8c` - clusters::reactivate:refactored for eth migration +6. `334414f` - clusters::deposit:refactored for eth migration +7. `a6269b0` - clusters::withdraw:refactored for eth migration +8. `800f6ac` - operators::library:refactored for eth migration +9. `925f11f` - operators::registerOperator:refactored for eth migration +10. `e71b395` - operators::removeOperator:refactored for eth migration remove operator ssv function added for backward, clusters missing functions added +11. `63cda69` - operators::declareOperatorFee:refactored for eth migration +12. `a0a87ff` - operators::reduceOperatorFee:refactored for eth migration +13. `ab8d658` - operators::withdraw:refactored for eth migration +14. `9db14fd` - SSVDAO:refactored for eth migration +15. `8377c83` - reentrancy guard added for eth payments + +--- + +## Testing Recommendations + +1. **Unit Tests:** Test all new ETH-based functions +2. **Integration Tests:** Test interaction between ETH and SSV token systems +3. **Migration Tests:** Test operator/cluster migration scenarios +4. **Security Tests:** Test reentrancy protection +5. **Backward Compatibility Tests:** Ensure existing SSV token operations continue to work +6. **Gas Optimization Tests:** Compare gas costs between ETH and SSV token operations + +--- + +## Notes + +- The migration maintains full backward compatibility with existing SSV token-based operations +- ETH and SSV token systems operate independently with separate storage and tracking +- Operators can migrate from SSV to ETH when executing a fee change +- All ETH transfer operations are protected against reentrancy attacks +- The version system ensures type safety and prevents incorrect operations + +--- diff --git a/contracts/SSVNetwork.sol b/contracts/SSVNetwork.sol index 469c4aef..fd6107f6 100644 --- a/contracts/SSVNetwork.sol +++ b/contracts/SSVNetwork.sol @@ -23,10 +23,12 @@ import {SSVModules} from "./libraries/SSVStorage.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; contract SSVNetwork is UUPSUpgradeable, Ownable2StepUpgradeable, + ReentrancyGuardUpgradeable, ISSVNetwork, ISSVOperators, ISSVOperatorsWhitelist, @@ -55,6 +57,7 @@ contract SSVNetwork is ) external override initializer onlyProxy { __UUPSUpgradeable_init(); __Ownable_init_unchained(); + __ReentrancyGuard_init(); __SSVNetwork_init_unchained( token_, ssvOperators_, @@ -129,7 +132,11 @@ contract SSVNetwork is _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); } - function removeOperator(uint64 operatorId) external override { + function removeOperator(uint64 operatorId) external override nonReentrant { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); + } + + function migrateOperatorToETH(uint64 operatorId) external override { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); } @@ -158,7 +165,7 @@ contract SSVNetwork is _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); } - function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external { + function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external override { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); } @@ -182,11 +189,23 @@ contract SSVNetwork is _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); } - function withdrawOperatorEarnings(uint64 operatorId, uint256 amount) external override { + function withdrawOperatorEarnings(uint64 operatorId, uint256 amount) external override nonReentrant { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); + } + + function withdrawAllOperatorEarnings(uint64 operatorId) external override nonReentrant { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); + } + + function withdrawAllVersionOperatorEarnings(uint64 operatorId) external override nonReentrant { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); + } + + function withdrawOperatorEarningsSSV(uint64 operatorId, uint256 amount) external override nonReentrant { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); } - function withdrawAllOperatorEarnings(uint64 operatorId) external override { + function withdrawAllOperatorEarningsSSV(uint64 operatorId) external override nonReentrant { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); } @@ -208,7 +227,7 @@ contract SSVNetwork is bytes calldata sharesData, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external payable override { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); } @@ -218,7 +237,7 @@ contract SSVNetwork is bytes[] calldata sharesData, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external payable override { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); } @@ -242,7 +261,15 @@ contract SSVNetwork is address clusterOwner, uint64[] calldata operatorIds, ISSVNetworkCore.Cluster memory cluster - ) external { + ) external override nonReentrant { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); + } + + function liquidateSSV( + address clusterOwner, + uint64[] calldata operatorIds, + ISSVNetworkCore.Cluster memory cluster + ) external override nonReentrant { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); } @@ -250,7 +277,7 @@ contract SSVNetwork is uint64[] calldata operatorIds, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external payable override { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); } @@ -259,7 +286,7 @@ contract SSVNetwork is uint64[] calldata operatorIds, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external payable override { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); } @@ -267,7 +294,15 @@ contract SSVNetwork is uint64[] calldata operatorIds, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external override nonReentrant { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); + } + + function migrateClusterToETH(uint64[] calldata operatorIds, ISSVNetworkCore.Cluster memory cluster) + external + payable + override + { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); } @@ -283,7 +318,15 @@ contract SSVNetwork is _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_DAO]); } - function withdrawNetworkEarnings(uint256 amount) external override onlyOwner { + function updateNetworkFeeSSV(uint256 fee) external override onlyOwner { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_DAO]); + } + + function withdrawNetworkEarnings(uint256 amount) external override onlyOwner nonReentrant { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_DAO]); + } + + function withdrawNetworkSSVEarnings(uint256 amount) external override onlyOwner nonReentrant { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_DAO]); } diff --git a/contracts/SSVNetworkViews.sol b/contracts/SSVNetworkViews.sol index 18159e58..21dab978 100644 --- a/contracts/SSVNetworkViews.sol +++ b/contracts/SSVNetworkViews.sol @@ -51,6 +51,10 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getOperatorFee(operatorId); } + function getOperatorFeeSSV(uint64 operatorId) external view override returns (uint256) { + return ssvNetwork.getOperatorFeeSSV(operatorId); + } + function getOperatorDeclaredFee(uint64 operatorId) external view override returns (bool, uint256, uint64, uint64) { return ssvNetwork.getOperatorDeclaredFee(operatorId); } @@ -61,6 +65,12 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getOperatorById(operatorId); } + function getOperatorByIdSSV( + uint64 operatorId + ) external view override returns (address, uint256, uint32, address, bool, bool) { + return ssvNetwork.getOperatorByIdSSV(operatorId); + } + function getWhitelistedOperators( uint64[] calldata operatorIds, address whitelistedAddress @@ -92,6 +102,14 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.isLiquidatable(clusterOwner, operatorIds, cluster); } + function isLiquidatableSSV( + address clusterOwner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view override returns (bool) { + return ssvNetwork.isLiquidatableSSV(clusterOwner, operatorIds, cluster); + } + function isLiquidated( address clusterOwner, uint64[] calldata operatorIds, @@ -104,10 +122,18 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews address clusterOwner, uint64[] calldata operatorIds, Cluster memory cluster - ) external view returns (uint256) { + ) external view override returns (uint256) { return ssvNetwork.getBurnRate(clusterOwner, operatorIds, cluster); } + function getBurnRateSSV( + address clusterOwner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view override returns (uint256) { + return ssvNetwork.getBurnRateSSV(clusterOwner, operatorIds, cluster); + } + /***********************************/ /* Balance External View Functions */ /***********************************/ @@ -116,6 +142,10 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getOperatorEarnings(id); } + function getOperatorEarningsSSV(uint64 id) external view override returns (uint256) { + return ssvNetwork.getOperatorEarningsSSV(id); + } + function getBalance( address clusterOwner, uint64[] calldata operatorIds, @@ -124,6 +154,14 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getBalance(clusterOwner, operatorIds, cluster); } + function getBalanceSSV( + address clusterOwner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view override returns (uint256) { + return ssvNetwork.getBalanceSSV(clusterOwner, operatorIds, cluster); + } + /*******************************/ /* DAO External View Functions */ /*******************************/ @@ -136,6 +174,14 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getNetworkEarnings(); } + function getNetworkFeeSSV() external view override returns (uint256) { + return ssvNetwork.getNetworkFeeSSV(); + } + + function getNetworkEarningsSSV() external view override returns (uint256) { + return ssvNetwork.getNetworkEarningsSSV(); + } + function getOperatorFeeIncreaseLimit() external view override returns (uint64) { return ssvNetwork.getOperatorFeeIncreaseLimit(); } @@ -164,6 +210,10 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getNetworkValidatorsCount(); } + function getClusterVersion(address owner, uint64[] calldata operatorIds) external view override returns (uint8) { + return ssvNetwork.getClusterVersion(owner, operatorIds); + } + function getVersion() external view override returns (string memory) { return ssvNetwork.getVersion(); } diff --git a/contracts/interfaces/ISSVClusters.sol b/contracts/interfaces/ISSVClusters.sol index 0da81ff8..75bbc5d3 100644 --- a/contracts/interfaces/ISSVClusters.sol +++ b/contracts/interfaces/ISSVClusters.sol @@ -16,7 +16,7 @@ interface ISSVClusters is ISSVNetworkCore { bytes calldata sharesData, uint256 amount, Cluster memory cluster - ) external; + ) external payable; /// @notice Registers new validators on the SSV Network /// @param publicKeys The public keys of the new validators @@ -30,7 +30,7 @@ interface ISSVClusters is ISSVNetworkCore { bytes[] calldata sharesData, uint256 amount, Cluster memory cluster - ) external; + ) external payable; /// @notice Removes an existing validator from the SSV Network /// @param publicKey The public key of the validator to be removed @@ -49,6 +49,11 @@ interface ISSVClusters is ISSVNetworkCore { Cluster memory cluster ) external; + /// @notice Migrates an SSV cluster to ETH, returning any SSV balance and accepting ETH top-up + /// @param operatorIds Array of operator IDs in the cluster + /// @param cluster Cluster data to migrate + function migrateClusterToETH(uint64[] calldata operatorIds, Cluster memory cluster) external payable; + /**************************/ /* Cluster External Functions */ /**************************/ @@ -59,11 +64,17 @@ interface ISSVClusters is ISSVNetworkCore { /// @param cluster Cluster to be liquidated function liquidate(address owner, uint64[] memory operatorIds, Cluster memory cluster) external; + /// @notice Liquidates a cluster + /// @param owner The owner of the cluster + /// @param operatorIds Array of IDs of operators managing the cluster + /// @param cluster Cluster to be liquidated + function liquidateSSV(address owner, uint64[] memory operatorIds, Cluster memory cluster) external; + /// @notice Reactivates a cluster /// @param operatorIds Array of IDs of operators managing the cluster /// @param amount Amount of SSV tokens to be deposited for reactivation /// @param cluster Cluster to be reactivated - function reactivate(uint64[] memory operatorIds, uint256 amount, Cluster memory cluster) external; + function reactivate(uint64[] memory operatorIds, uint256 amount, Cluster memory cluster) external payable; /******************************/ /* Balance External Functions */ @@ -74,7 +85,12 @@ interface ISSVClusters is ISSVNetworkCore { /// @param operatorIds Array of IDs of operators managing the cluster /// @param amount Amount of SSV tokens to be deposited /// @param cluster Cluster where the deposit will be made - function deposit(address owner, uint64[] memory operatorIds, uint256 amount, Cluster memory cluster) external; + function deposit( + address owner, + uint64[] memory operatorIds, + uint256 amount, + Cluster memory cluster + ) external payable; /// @notice Withdraws tokens from a cluster /// @param operatorIds Array of IDs of operators managing the cluster @@ -125,6 +141,22 @@ interface ISSVClusters is ISSVNetworkCore { */ event ClusterReactivated(address indexed owner, uint64[] operatorIds, Cluster cluster); + /** + * @dev Emitted when a legacy SSV cluster is migrated to ETH. + * @param owner The owner of the migrated cluster. + * @param operatorIds The operator IDs managing the cluster. + * @param ethDeposited The amount of ETH supplied during migration. + * @param ssvRefunded The amount of SSV tokens refunded to the owner. + * @param cluster The migrated cluster data (ETH version). + */ + event ClusterMigratedToETH( + address indexed owner, + uint64[] operatorIds, + uint256 ethDeposited, + uint256 ssvRefunded, + Cluster cluster + ); + /** * @dev Emitted when tokens are withdrawn from a cluster. * @param owner The owner of the cluster. diff --git a/contracts/interfaces/ISSVDAO.sol b/contracts/interfaces/ISSVDAO.sol index a91dc3b5..9c55aebf 100644 --- a/contracts/interfaces/ISSVDAO.sol +++ b/contracts/interfaces/ISSVDAO.sol @@ -4,14 +4,22 @@ pragma solidity ^0.8.20; import {ISSVNetworkCore} from "./ISSVNetworkCore.sol"; interface ISSVDAO is ISSVNetworkCore { - /// @notice Updates the network fee - /// @param fee The new network fee (SSV) to be set + /// @notice Updates the network fee (ETH post-migration) + /// @param fee The new network fee (ETH) to be set function updateNetworkFee(uint256 fee) external; - /// @notice Withdraws network earnings - /// @param amount The amount (SSV) to be withdrawn + /// @notice Updates the legacy network fee (SSV pre-migration) + /// @param fee The new network fee (SSV) to be set + function updateNetworkFeeSSV(uint256 fee) external; + + /// @notice Withdraws network earnings (ETH post-migration) + /// @param amount The amount (ETH) to be withdrawn function withdrawNetworkEarnings(uint256 amount) external; + /// @notice Withdraws legacy network earnings (SSV pre-migration) + /// @param amount The amount (SSV) to be withdrawn + function withdrawNetworkSSVEarnings(uint256 amount) external; + /// @notice Updates the limit on the percentage increase in operator fees /// @param percentage The new percentage limit function updateOperatorFeeIncreaseLimit(uint64 percentage) external; diff --git a/contracts/interfaces/ISSVNetworkCore.sol b/contracts/interfaces/ISSVNetworkCore.sol index b36812ae..c7aa96b0 100644 --- a/contracts/interfaces/ISSVNetworkCore.sol +++ b/contracts/interfaces/ISSVNetworkCore.sol @@ -28,6 +28,14 @@ interface ISSVNetworkCore { bool whitelisted; /// @dev The state snapshot of the operator Snapshot snapshot; + /// @dev Operator struct version 0, version 0 fee = SSV, version 1 fee = eth. + uint8 version; + /// @dev The number of validators associated with this operator in eth + uint32 ethValidatorCount; + /// @dev The fee charged by the operator in eth, set to zero for private operators and cannot be increased once set + uint64 ethFee; + /// @dev The state snapshot of the operator for eth + Snapshot ethSnapshot; } /// @notice Represents a request to change an operator's fee @@ -95,6 +103,9 @@ interface ISSVNetworkCore { error InvalidWhitelistingContract(address contractAddress); // 0x886e6a03 error InvalidWhitelistAddressesLength(); // 0xcbb362dc error ZeroAddressNotAllowed(); // 0x8579befe + error IncorrectOperatorVersion(uint8 operatorVersion); // 0xf222e863 + error IncorrectClusterVersion(); // 0xf6749746 + error ETHTransferFailed(); // 0xb12d13eb // legacy errors error ValidatorAlreadyExists(); // 0x8d09a73e diff --git a/contracts/interfaces/ISSVOperators.sol b/contracts/interfaces/ISSVOperators.sol index 19548c37..b724c535 100644 --- a/contracts/interfaces/ISSVOperators.sol +++ b/contracts/interfaces/ISSVOperators.sol @@ -4,16 +4,20 @@ pragma solidity ^0.8.20; import {ISSVNetworkCore} from "./ISSVNetworkCore.sol"; interface ISSVOperators is ISSVNetworkCore { - /// @notice Registers a new operator + /// @notice Registers a new operator (ETH version post-migration) /// @param publicKey The public key of the operator - /// @param fee The operator's fee (SSV) + /// @param fee The operator's fee (ETH) /// @param setPrivate Flag indicating whether the operator should be set as private or not function registerOperator(bytes calldata publicKey, uint256 fee, bool setPrivate) external returns (uint64); - /// @notice Removes an existing operator + /// @notice Removes an existing ETH operator /// @param operatorId The ID of the operator to be removed function removeOperator(uint64 operatorId) external; + /// @notice Migrates a legacy SSV operator to ETH with a default ETH fee + /// @param operatorId The ID of the operator to migrate + function migrateOperatorToETH(uint64 operatorId) external; + /// @notice Declares the operator's fee /// @param operatorId The ID of the operator /// @param fee The fee to be declared (SSV) @@ -32,22 +36,35 @@ interface ISSVOperators is ISSVNetworkCore { /// @param fee The new Operator's fee (SSV) function reduceOperatorFee(uint64 operatorId, uint256 fee) external; - /// @notice Withdraws operator earnings + /// @notice Withdraws operator earnings in ETH (post-migration) /// @param operatorId The ID of the operator - /// @param tokenAmount The amount of tokens to withdraw (SSV) - function withdrawOperatorEarnings(uint64 operatorId, uint256 tokenAmount) external; + /// @param ethAmount The amount of ETH-denominated earnings to withdraw + function withdrawOperatorEarnings(uint64 operatorId, uint256 ethAmount) external; - /// @notice Withdraws all operator earnings + /// @notice Withdraws all operator earnings in ETH (post-migration) /// @param operatorId The ID of the operator function withdrawAllOperatorEarnings(uint64 operatorId) external; + /// @notice Withdraws all operator earnings (both ETH and legacy SSV) in a single call + /// @param operatorId The ID of the operator + function withdrawAllVersionOperatorEarnings(uint64 operatorId) external; + + /// @notice Withdraws operator earnings in SSV (legacy pre-migration) + /// @param operatorId The ID of the operator + /// @param tokenAmount The amount of tokens to withdraw (SSV) + function withdrawOperatorEarningsSSV(uint64 operatorId, uint256 tokenAmount) external; + + /// @notice Withdraws all operator earnings in SSV (legacy pre-migration) + /// @param operatorId The ID of the operator + function withdrawAllOperatorEarningsSSV(uint64 operatorId) external; + /// @notice Set the list of operators as private without checking for any whitelisting address /// @notice The operators are considered private when registering validators /// @param operatorIds The operator IDs to set as private function setOperatorsPrivateUnchecked(uint64[] calldata operatorIds) external; /// @notice Set the list of operators as public without removing any whitelisting address - /// @notice The operators still keep its adresses whitelisted (external contract or EOAs/generic contracts) + /// @notice The operators still keep its addresses whitelisted (external contract or EOAs/generic contracts) /// @notice The operators are considered public when registering validators /// @param operatorIds The operator IDs to set as public function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external; diff --git a/contracts/interfaces/ISSVOperatorsWhitelist.sol b/contracts/interfaces/ISSVOperatorsWhitelist.sol index fea9fd53..f2494e32 100644 --- a/contracts/interfaces/ISSVOperatorsWhitelist.sol +++ b/contracts/interfaces/ISSVOperatorsWhitelist.sol @@ -30,14 +30,14 @@ interface ISSVOperatorsWhitelist is ISSVNetworkCore { function removeOperatorsWhitelistingContract(uint64[] calldata operatorIds) external; /** - * @dev Emitted when a list of adresses are whitelisted for a set of operators. + * @dev Emitted when a list of addresses are whitelisted for a set of operators. * @param operatorIds operators' IDs. * @param whitelistAddresses operators' new whitelist addresses (EOAs or generic contracts). */ event OperatorMultipleWhitelistUpdated(uint64[] operatorIds, address[] whitelistAddresses); /** - * @dev Emitted when a list of adresses are de-whitelisted for a set of operators. + * @dev Emitted when a list of addresses are de-whitelisted for a set of operators. * @param operatorIds operators' IDs. * @param whitelistAddresses operators' list of whitelist addresses to be removed (EOAs or generic contracts). */ diff --git a/contracts/interfaces/ISSVViews.sol b/contracts/interfaces/ISSVViews.sol index 2e06ae31..2be3921a 100644 --- a/contracts/interfaces/ISSVViews.sol +++ b/contracts/interfaces/ISSVViews.sol @@ -12,9 +12,14 @@ interface ISSVViews is ISSVNetworkCore { /// @notice Gets the operator fee /// @param operatorId The ID of the operator - /// @return fee The fee associated with the operator (SSV). If the operator does not exist, the returned value is 0. + /// @return fee The fee associated with the operator (ETH). If the operator does not exist, the returned value is 0. function getOperatorFee(uint64 operatorId) external view returns (uint256 fee); + /// @notice Gets the legacy SSV operator fee + /// @param operatorId The ID of the operator + /// @return fee The fee associated with the operator (SSV). If the operator does not exist, the returned value is 0. + function getOperatorFeeSSV(uint64 operatorId) external view returns (uint256 fee); + /// @notice Gets the declared operator fee /// @param operatorId The ID of the operator /// @return isFeeDeclared A boolean indicating if the fee is declared @@ -28,13 +33,35 @@ interface ISSVViews is ISSVNetworkCore { /// @notice Gets operator details by ID /// @param operatorId The ID of the operator /// @return owner The owner of the operator - /// @return fee The fee associated with the operator (SSV) - /// @return validatorCount The count of validators associated with the operator + /// @return ethFee The fee associated with the operator (ETH) + /// @return ethValidatorCount The count of validators associated with the operator (ETH) /// @return whitelistedAddress The whitelisted address of the operator. It can be and EOA or generic contract (legacy) or a whitelisting contract /// @return isPrivate A boolean indicating if the operator is private (uses whitelisting contract or SSV Whitelisting module) - /// @return active A boolean indicating if the operator is active + /// @return active A boolean indicating if the operator is active (ETH snapshot initialized) function getOperatorById( uint64 operatorId + ) + external + view + returns ( + address owner, + uint256 ethFee, + uint32 ethValidatorCount, + address whitelistedAddress, + bool isPrivate, + bool active + ); + + /// @notice Gets legacy SSV operator details by ID + /// @param operatorId The ID of the operator + /// @return owner The owner of the operator + /// @return fee The fee associated with the operator (SSV) + /// @return validatorCount The count of validators associated with the operator (SSV) + /// @return whitelistedAddress The whitelisted address of the operator. It can be and EOA or generic contract (legacy) or a whitelisting contract + /// @return isPrivate A boolean indicating if the operator is private (uses whitelisting contract or SSV Whitelisting module) + /// @return active A boolean indicating if the operator is active (SSV snapshot initialized) + function getOperatorByIdSSV( + uint64 operatorId ) external view @@ -83,6 +110,16 @@ interface ISSVViews is ISSVNetworkCore { Cluster memory cluster ) external view returns (bool isLiquidatable); + /// @notice Checks if the legacy SSV cluster can be liquidated + /// @param owner The owner address of the cluster + /// @param operatorIds The IDs of the operators in the cluster + /// @return isLiquidatable A boolean indicating if the cluster can be liquidated + function isLiquidatableSSV( + address owner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view returns (bool isLiquidatable); + /// @notice Checks if the cluster is liquidated /// @param owner The owner address of the cluster /// @param operatorIds The IDs of the operators in the cluster @@ -103,29 +140,68 @@ interface ISSVViews is ISSVNetworkCore { Cluster memory cluster ) external view returns (uint256 burnRate); + /// @notice Gets the burn rate of the legacy SSV cluster + /// @param owner The owner address of the cluster + /// @param operatorIds The IDs of the operators in the cluster + /// @return burnRate The burn rate of the cluster (SSV) + function getBurnRateSSV( + address owner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view returns (uint256 burnRate); + /// @notice Gets operator earnings /// @param operatorId The ID of the operator - /// @return earnings The earnings associated with the operator (SSV) + /// @return earnings The earnings associated with the operator (ETH) function getOperatorEarnings(uint64 operatorId) external view returns (uint256 earnings); + /// @notice Gets legacy SSV operator earnings + /// @param operatorId The ID of the operator + /// @return earnings The earnings associated with the operator (SSV) + function getOperatorEarningsSSV(uint64 operatorId) external view returns (uint256 earnings); + /// @notice Gets the balance of the cluster /// @param owner The owner address of the cluster /// @param operatorIds The IDs of the operators in the cluster - /// @return balance The balance of the cluster (SSV) + /// @return balance The balance of the cluster (ETH) function getBalance( address owner, uint64[] memory operatorIds, Cluster memory cluster ) external view returns (uint256 balance); + /// @notice Gets the balance of the legacy SSV cluster + /// @param owner The owner address of the cluster + /// @param operatorIds The IDs of the operators in the cluster + /// @return balance The balance of the cluster (SSV) + function getBalanceSSV( + address owner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view returns (uint256 balance); + + /// @notice Gets the version of a cluster (ETH or SSV) + /// @param owner The owner address of the cluster + /// @param operatorIds The IDs of the operators in the cluster + /// @return version The cluster version (see CoreLib.VERSION_* constants) + function getClusterVersion(address owner, uint64[] calldata operatorIds) external view returns (uint8 version); + /// @notice Gets the network fee - /// @return networkFee The fee associated with the network (SSV) + /// @return networkFee The fee associated with the network (ETH) function getNetworkFee() external view returns (uint256 networkFee); /// @notice Gets the network earnings - /// @return networkEarnings The earnings associated with the network (SSV) + /// @return networkEarnings The earnings associated with the network (ETH) function getNetworkEarnings() external view returns (uint256 networkEarnings); + /// @notice Gets the legacy network fee (SSV pre-migration) + /// @return networkFee The fee associated with the network (SSV) + function getNetworkFeeSSV() external view returns (uint256 networkFee); + + /// @notice Gets the legacy network earnings (SSV pre-migration) + /// @return networkEarnings The earnings associated with the network (SSV) + function getNetworkEarningsSSV() external view returns (uint256 networkEarnings); + /// @notice Gets the operator fee increase limit /// @return The maximum limit of operator fee increase function getOperatorFeeIncreaseLimit() external view returns (uint64); diff --git a/contracts/libraries/ClusterLib.sol b/contracts/libraries/ClusterLib.sol index 0a231e4d..9bc4e91d 100644 --- a/contracts/libraries/ClusterLib.sol +++ b/contracts/libraries/ClusterLib.sol @@ -7,6 +7,7 @@ import {StorageProtocol} from "./SSVStorageProtocol.sol"; import "./OperatorLib.sol"; import "./ProtocolLib.sol"; import {Types64} from "./Types.sol"; +import "./CoreLib.sol"; library ClusterLib { using Types64 for uint64; @@ -48,16 +49,18 @@ library ClusterLib { address owner, uint64[] memory operatorIds, StorageData storage s - ) internal view returns (bytes32 hashedCluster) { + ) internal view returns (bytes32 hashedCluster, uint8 version) { hashedCluster = keccak256(abi.encodePacked(owner, operatorIds)); bytes32 hashedClusterData = hashClusterData(cluster); - bytes32 clusterData = s.clusters[hashedCluster]; + (bytes32 clusterData, uint8 detectedVersion) = getClusterData(hashedCluster, s); if (clusterData == bytes32(0)) { revert ISSVNetworkCore.ClusterDoesNotExists(); } else if (clusterData != hashedClusterData) { revert ISSVNetworkCore.IncorrectClusterState(); } + + return (hashedCluster, detectedVersion); } function updateClusterData( @@ -90,7 +93,7 @@ library ClusterLib { ) internal view returns (bytes32 hashedCluster) { hashedCluster = keccak256(abi.encodePacked(msg.sender, operatorIds)); - bytes32 clusterData = s.clusters[hashedCluster]; + bytes32 clusterData = s.ethClusters[hashedCluster]; if (clusterData == bytes32(0)) { if ( cluster.validatorCount != 0 || @@ -133,7 +136,7 @@ library ClusterLib { isLiquidatable( cluster, burnRate, - sp.networkFee, + sp.ethNetworkFee, sp.minimumBlocksBeforeLiquidation, sp.minimumLiquidationCollateral ) @@ -141,6 +144,27 @@ library ClusterLib { revert ISSVNetworkCore.InsufficientBalance(); } - s.clusters[hashedCluster] = hashClusterData(cluster); + s.ethClusters[hashedCluster] = hashClusterData(cluster); + } + + function validateClusterVersion(uint8 clusterVersion, uint8 expectedVersion) internal pure { + if (clusterVersion != expectedVersion) revert ISSVNetworkCore.IncorrectClusterVersion(); + } + + function getClusterData( + bytes32 hashedCluster, + StorageData storage s + ) internal view returns (bytes32 clusterData, uint8 version) { + clusterData = s.ethClusters[hashedCluster]; + if (clusterData != bytes32(0)) { + return (clusterData, CoreLib.VERSION_ETH); + } + + clusterData = s.clusters[hashedCluster]; + if (clusterData != bytes32(0)) { + return (clusterData, CoreLib.VERSION_SSV); + } + + revert ISSVNetworkCore.ClusterDoesNotExists(); } } diff --git a/contracts/libraries/CoreLib.sol b/contracts/libraries/CoreLib.sol index 6e17bd66..fd1702be 100644 --- a/contracts/libraries/CoreLib.sol +++ b/contracts/libraries/CoreLib.sol @@ -5,19 +5,23 @@ import "./SSVStorage.sol"; library CoreLib { event ModuleUpgraded(SSVModules indexed moduleId, address moduleAddress); + + uint8 internal constant VERSION_SSV = 0; + uint8 internal constant VERSION_ETH = 1; + uint8 internal constant VERSION_UNDEFINED = type(uint8).max; function getVersion() internal pure returns (string memory) { - return "v1.2.0"; + return "v1.3.0"; } - + //TODO: Add reentrancy modifier here function transferBalance(address to, uint256 amount) internal { - if (!SSVStorage.load().token.transfer(to, amount)) { - revert ISSVNetworkCore.TokenTransferFailed(); + (bool success, ) = payable(to).call{value: amount}(""); + if(!success){ + revert ISSVNetworkCore.ETHTransferFailed(); } } - - function deposit(uint256 amount) internal { - if (!SSVStorage.load().token.transferFrom(msg.sender, address(this), amount)) { + function transferTokenBalance(address to, uint256 amount) internal { + if (!SSVStorage.load().token.transfer(to, amount)) { revert ISSVNetworkCore.TokenTransferFailed(); } } diff --git a/contracts/libraries/OperatorLib.sol b/contracts/libraries/OperatorLib.sol index 3fdc17ef..b0dbb73a 100644 --- a/contracts/libraries/OperatorLib.sol +++ b/contracts/libraries/OperatorLib.sol @@ -6,13 +6,31 @@ import {ISSVWhitelistingContract} from "../interfaces/external/ISSVWhitelistingC import {StorageData} from "./SSVStorage.sol"; import {StorageProtocol} from "./SSVStorageProtocol.sol"; import {Types64} from "./Types.sol"; +import "./CoreLib.sol"; import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; library OperatorLib { using Types64 for uint64; + uint64 internal constant DEFAULT_OPERATOR_ETH_FEE = 1_000_000_000; + function updateSnapshot(ISSVNetworkCore.Operator memory operator) internal view { + uint32 currentBlock = uint32(block.number); + uint64 blockDiffEthFee = (currentBlock - operator.ethSnapshot.block) * operator.ethFee; + operator.ethSnapshot.index += blockDiffEthFee; + operator.ethSnapshot.balance += blockDiffEthFee * operator.ethValidatorCount; + operator.ethSnapshot.block = currentBlock; + } + + function updateSnapshotSt(ISSVNetworkCore.Operator storage operator) internal { + uint32 currentBlock = uint32(block.number); + uint64 blockDiffEthFee = (currentBlock - operator.ethSnapshot.block) * operator.ethFee; + operator.ethSnapshot.index += blockDiffEthFee; + operator.ethSnapshot.balance += blockDiffEthFee * operator.ethValidatorCount; + operator.ethSnapshot.block = currentBlock; + } + function updateSnapshotSSV(ISSVNetworkCore.Operator memory operator) internal view { uint64 blockDiffFee = (uint32(block.number) - operator.snapshot.block) * operator.fee; operator.snapshot.index += blockDiffFee; @@ -20,19 +38,49 @@ library OperatorLib { operator.snapshot.block = uint32(block.number); } - function updateSnapshotSt(ISSVNetworkCore.Operator storage operator) internal { + function updateSnapshotStSVV(ISSVNetworkCore.Operator storage operator) internal { uint64 blockDiffFee = (uint32(block.number) - operator.snapshot.block) * operator.fee; operator.snapshot.index += blockDiffFee; operator.snapshot.balance += blockDiffFee * operator.validatorCount; operator.snapshot.block = uint32(block.number); } + function updateSnapshots(ISSVNetworkCore.Operator memory operator) internal view { + updateSnapshot(operator); + updateSnapshotSSV(operator); + } + + function updateSnapshotsSt(ISSVNetworkCore.Operator storage operator) internal { + updateSnapshotSt(operator); + updateSnapshotStSVV(operator); + } + + function defaultOperatorEthFee() internal pure returns (uint64) { + return DEFAULT_OPERATOR_ETH_FEE; + } function checkOwner(ISSVNetworkCore.Operator memory operator) internal view { - if (operator.snapshot.block == 0) revert ISSVNetworkCore.OperatorDoesNotExist(); + if (operator.snapshot.block == 0 && operator.ethSnapshot.block == 0) { + revert ISSVNetworkCore.OperatorDoesNotExist(); + } if (operator.owner != msg.sender) revert ISSVNetworkCore.CallerNotOwnerWithData(msg.sender, operator.owner); } + function ensureETHDefaults(ISSVNetworkCore.Operator storage operator) internal { + if (operator.version != CoreLib.VERSION_ETH) { + if (operator.ethSnapshot.block == 0) { + operator.ethSnapshot = ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}); + } + if (operator.fee != 0) { + if (operator.ethFee == 0) { + operator.ethFee = DEFAULT_OPERATOR_ETH_FEE; + } + } else { + operator.version = CoreLib.VERSION_ETH; + } + } + } + function updateClusterOperatorsOnRegistration( uint64[] memory operatorIds, uint32 deltaValidatorCount, @@ -56,9 +104,9 @@ library OperatorLib { } } ISSVNetworkCore.Operator memory operator = s.operators[operatorId]; - - if (operator.snapshot.block == 0) { - revert ISSVNetworkCore.OperatorDoesNotExist(); + if (operator.version != CoreLib.VERSION_ETH) { + ensureETHDefaults(s.operators[operatorId]); + operator = s.operators[operatorId]; } // check if the pending operator is whitelisted (must be backward compatible) @@ -92,12 +140,11 @@ library OperatorLib { } updateSnapshot(operator); - if ((operator.validatorCount += deltaValidatorCount) > sp.validatorsPerOperatorLimit) { + if ((operator.ethValidatorCount += deltaValidatorCount) > sp.validatorsPerOperatorLimit) { revert ISSVNetworkCore.ExceedValidatorLimitWithData(operatorId); } - - cumulativeFee += operator.fee; - cumulativeIndex += operator.snapshot.index; + cumulativeFee += operator.ethFee; + cumulativeIndex += operator.ethSnapshot.index; s.operators[operatorId] = operator; } @@ -117,8 +164,48 @@ library OperatorLib { ISSVNetworkCore.Operator storage operator = s.operators[operatorId]; - if (operator.snapshot.block != 0) { + if (operator.version != CoreLib.VERSION_ETH) { + updateSnapshotStSVV(operator); + if (increaseValidatorCount) { + operator.validatorCount -= deltaValidatorCount; + } + ensureETHDefaults(operator); + } + + if (operator.ethSnapshot.block != 0) { updateSnapshotSt(operator); + if (!increaseValidatorCount) { + operator.ethValidatorCount -= deltaValidatorCount; + } else if ((operator.ethValidatorCount += deltaValidatorCount) > sp.validatorsPerOperatorLimit) { + revert ISSVNetworkCore.ExceedValidatorLimitWithData(operatorId); + } + + cumulativeFee += operator.ethFee; + } + cumulativeIndex += operator.ethSnapshot.index; + } + } + + function updateClusterOperatorsSSV( + uint64[] memory operatorIds, + bool increaseValidatorCount, + uint32 deltaValidatorCount, + StorageData storage s, + StorageProtocol storage sp + ) internal returns (uint64 cumulativeIndex, uint64 cumulativeFee) { + uint256 operatorsLength = operatorIds.length; + + for (uint256 i; i < operatorsLength; ++i) { + uint64 operatorId = operatorIds[i]; + + ISSVNetworkCore.Operator storage operator = s.operators[operatorId]; + + if (operator.version != CoreLib.VERSION_SSV) { + revert ISSVNetworkCore.IncorrectOperatorVersion(operator.version); + } + + if (operator.snapshot.block != 0) { + updateSnapshotStSVV(operator); if (!increaseValidatorCount) { operator.validatorCount -= deltaValidatorCount; } else if ((operator.validatorCount += deltaValidatorCount) > sp.validatorsPerOperatorLimit) { @@ -127,6 +214,7 @@ library OperatorLib { cumulativeFee += operator.fee; } + cumulativeIndex += operator.snapshot.index; } } diff --git a/contracts/libraries/ProtocolLib.sol b/contracts/libraries/ProtocolLib.sol index 1a839e23..c9fcd82d 100644 --- a/contracts/libraries/ProtocolLib.sol +++ b/contracts/libraries/ProtocolLib.sol @@ -12,13 +12,25 @@ library ProtocolLib { /* Network internal functions */ /******************************/ function currentNetworkFeeIndex(StorageProtocol storage sp) internal view returns (uint64) { + return sp.ethNetworkFeeIndex + uint64(block.number - sp.ethNetworkFeeIndexBlockNumber) * sp.ethNetworkFee; + } + + function currentNetworkFeeIndexSSV(StorageProtocol storage sp) internal view returns (uint64) { return sp.networkFeeIndex + uint64(block.number - sp.networkFeeIndexBlockNumber) * sp.networkFee; } function updateNetworkFee(StorageProtocol storage sp, uint256 fee) internal { updateDAOEarnings(sp); - sp.networkFeeIndex = currentNetworkFeeIndex(sp); + sp.ethNetworkFeeIndex = currentNetworkFeeIndex(sp); + sp.ethNetworkFeeIndexBlockNumber = uint32(block.number); + sp.ethNetworkFee = fee.shrink(); + } + + function updateNetworkFeeSSV(StorageProtocol storage sp, uint256 fee) internal { + updateDAOEarnings(sp); + + sp.networkFeeIndex = currentNetworkFeeIndexSSV(sp); sp.networkFeeIndexBlockNumber = uint32(block.number); sp.networkFee = fee.shrink(); } @@ -27,16 +39,38 @@ library ProtocolLib { /* DAO internal functions */ /**************************/ function updateDAOEarnings(StorageProtocol storage sp) internal { - sp.daoBalance = networkTotalEarnings(sp); + sp.ethDaoBalance = networkTotalEarnings(sp); + sp.ethDaoIndexBlockNumber = uint32(block.number); + } + + function updateDAOEarningsSSV(StorageProtocol storage sp) internal { + sp.daoBalance = networkTotalEarningsSSV(sp); sp.daoIndexBlockNumber = uint32(block.number); } function networkTotalEarnings(StorageProtocol storage sp) internal view returns (uint64) { + return + sp.ethDaoBalance + + (uint64(block.number - sp.ethDaoIndexBlockNumber)) * + sp.ethNetworkFee * + sp.ethDaoValidatorCount; + } + + function networkTotalEarningsSSV(StorageProtocol storage sp) internal view returns (uint64) { return sp.daoBalance + (uint64(block.number) - sp.daoIndexBlockNumber) * sp.networkFee * sp.daoValidatorCount; } function updateDAO(StorageProtocol storage sp, bool increaseValidatorCount, uint32 deltaValidatorCount) internal { updateDAOEarnings(sp); + if (!increaseValidatorCount) { + sp.ethDaoValidatorCount -= deltaValidatorCount; + } else if ((sp.ethDaoValidatorCount += deltaValidatorCount) > type(uint32).max) { + revert ISSVNetworkCore.MaxValueExceeded(); + } + } + + function updateDAOSSV(StorageProtocol storage sp, bool increaseValidatorCount, uint32 deltaValidatorCount) internal { + updateDAOEarningsSSV(sp); if (!increaseValidatorCount) { sp.daoValidatorCount -= deltaValidatorCount; } else if ((sp.daoValidatorCount += deltaValidatorCount) > type(uint32).max) { diff --git a/contracts/libraries/SSVStorage.sol b/contracts/libraries/SSVStorage.sol index c0ceaa99..1b4cc8ba 100644 --- a/contracts/libraries/SSVStorage.sol +++ b/contracts/libraries/SSVStorage.sol @@ -38,6 +38,9 @@ struct StorageData { /// @notice that are whitelisted for that address using bitmaps /// @dev The nested mapping's key represents a uint256 slot to handle more than 256 operators per address mapping(address => mapping(uint256 => uint256)) addressWhitelistedForOperators; + + /// @notice Maps each cluster's bytes32 identifier to its hashed representation of ISSVNetworkCore.Cluster for eth + mapping(bytes32 => bytes32) ethClusters; } library SSVStorage { diff --git a/contracts/libraries/SSVStorageProtocol.sol b/contracts/libraries/SSVStorageProtocol.sol index fa83d778..3ca7dd1a 100644 --- a/contracts/libraries/SSVStorageProtocol.sol +++ b/contracts/libraries/SSVStorageProtocol.sol @@ -30,6 +30,20 @@ struct StorageProtocol { uint64 operatorMaxFeeIncrease; /// @notice The maximum value in operator fee that is allowed (SSV) uint64 operatorMaxFee; + + //ETH + /// @notice The block number when the network fee index was last updated for eth + uint32 ethNetworkFeeIndexBlockNumber; + /// @notice The count of validators governed by the DAO for eth clusters + uint32 ethDaoValidatorCount; + /// @notice The block number when the DAO index was last updated for eth + uint32 ethDaoIndexBlockNumber; + /// @notice The current network fee value for eth clusters + uint64 ethNetworkFee; + /// @notice The current network fee index value for eth clusters + uint64 ethNetworkFeeIndex; + /// @notice The current balance of the DAO for eth clusters + uint64 ethDaoBalance; } library SSVStorageProtocol { diff --git a/contracts/modules/SSVClusters.sol b/contracts/modules/SSVClusters.sol index 3a77d1dc..ec30597c 100644 --- a/contracts/modules/SSVClusters.sol +++ b/contracts/modules/SSVClusters.sol @@ -19,9 +19,9 @@ contract SSVClusters is ISSVClusters { bytes calldata publicKey, uint64[] memory operatorIds, bytes calldata sharesData, - uint256 amount, + uint256, // deprecated amount param stays for backward compatability Cluster memory cluster - ) external override { + ) external payable override { StorageData storage s = SSVStorage.load(); StorageProtocol storage sp = SSVStorageProtocol.load(); @@ -31,14 +31,10 @@ contract SSVClusters is ISSVClusters { bytes32 hashedCluster = cluster.validateClusterOnRegistration(operatorIds, s); - cluster.balance += amount; + cluster.balance += msg.value; cluster.updateClusterOnRegistration(operatorIds, hashedCluster, 1, s, sp); - if (amount != 0) { - CoreLib.deposit(amount); - } - emit ValidatorAdded(msg.sender, operatorIds, publicKey, sharesData, cluster); } @@ -46,9 +42,9 @@ contract SSVClusters is ISSVClusters { bytes[] memory publicKeys, uint64[] memory operatorIds, bytes[] calldata sharesData, - uint256 amount, + uint256, // deprecated amount param stays for backward compatability Cluster memory cluster - ) external override { + ) external payable override { uint256 validatorsLength = publicKeys.length; if (validatorsLength == 0) revert EmptyPublicKeysList(); @@ -64,14 +60,10 @@ contract SSVClusters is ISSVClusters { } bytes32 hashedCluster = cluster.validateClusterOnRegistration(operatorIds, s); - cluster.balance += amount; + cluster.balance += msg.value; cluster.updateClusterOnRegistration(operatorIds, hashedCluster, uint32(validatorsLength), s, sp); - if (amount != 0) { - CoreLib.deposit(amount); - } - for (uint i; i < validatorsLength; ++i) { bytes memory pk = publicKeys[i]; bytes memory sh = sharesData[i]; @@ -87,7 +79,8 @@ contract SSVClusters is ISSVClusters { ) external override { StorageData storage s = SSVStorage.load(); - bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s); + (bytes32 hashedCluster, uint8 version) = cluster.validateHashedCluster(msg.sender, operatorIds, s); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_ETH); bytes32 hashedOperatorIds = ValidatorLib.hashOperatorIds(operatorIds); bytes32 hashedValidator = keccak256(abi.encodePacked(publicKey, msg.sender)); @@ -113,7 +106,7 @@ contract SSVClusters is ISSVClusters { --cluster.validatorCount; - s.clusters[hashedCluster] = cluster.hashClusterData(); + s.ethClusters[hashedCluster] = cluster.hashClusterData(); emit ValidatorRemoved(msg.sender, operatorIds, publicKey, cluster); } @@ -130,7 +123,8 @@ contract SSVClusters is ISSVClusters { } StorageData storage s = SSVStorage.load(); - bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s); + (bytes32 hashedCluster, uint8 version) = cluster.validateHashedCluster(msg.sender, operatorIds, s); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_ETH); bytes32 hashedOperatorIds = ValidatorLib.hashOperatorIds(operatorIds); bytes32 hashedValidator; @@ -160,17 +154,22 @@ contract SSVClusters is ISSVClusters { cluster.validatorCount -= validatorsRemoved; - s.clusters[hashedCluster] = cluster.hashClusterData(); + s.ethClusters[hashedCluster] = cluster.hashClusterData(); for (uint i; i < validatorsLength; ++i) { emit ValidatorRemoved(msg.sender, operatorIds, publicKeys[i], cluster); } } - function liquidate(address clusterOwner, uint64[] calldata operatorIds, Cluster memory cluster) external override { + function liquidate( + address clusterOwner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external override { StorageData storage s = SSVStorage.load(); - bytes32 hashedCluster = cluster.validateHashedCluster(clusterOwner, operatorIds, s); + (bytes32 hashedCluster, uint8 version) = cluster.validateHashedCluster(msg.sender, operatorIds, s); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_ETH); cluster.validateClusterIsNotLiquidated(); StorageProtocol storage sp = SSVStorageProtocol.load(); @@ -191,7 +190,7 @@ contract SSVClusters is ISSVClusters { clusterOwner != msg.sender && !cluster.isLiquidatable( burnRate, - sp.networkFee, + sp.ethNetworkFee, sp.minimumBlocksBeforeLiquidation, sp.minimumLiquidationCollateral ) @@ -209,7 +208,7 @@ contract SSVClusters is ISSVClusters { cluster.networkFeeIndex = 0; cluster.active = false; - s.clusters[hashedCluster] = cluster.hashClusterData(); + s.ethClusters[hashedCluster] = cluster.hashClusterData(); if (balanceLiquidatable != 0) { CoreLib.transferBalance(msg.sender, balanceLiquidatable); @@ -218,10 +217,71 @@ contract SSVClusters is ISSVClusters { emit ClusterLiquidated(clusterOwner, operatorIds, cluster); } - function reactivate(uint64[] calldata operatorIds, uint256 amount, Cluster memory cluster) external override { + function liquidateSSV( + address clusterOwner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external override { StorageData storage s = SSVStorage.load(); - bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s); + (bytes32 hashedCluster, uint8 version) = cluster.validateHashedCluster(msg.sender, operatorIds, s); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_SSV); + cluster.validateClusterIsNotLiquidated(); + + StorageProtocol storage sp = SSVStorageProtocol.load(); + + (uint64 clusterIndex, uint64 burnRate) = OperatorLib.updateClusterOperatorsSSV( + operatorIds, + false, + cluster.validatorCount, + s, + sp + ); + + cluster.updateBalance(clusterIndex, sp.currentNetworkFeeIndexSSV()); + + uint256 balanceLiquidatable; + + if ( + clusterOwner != msg.sender && + !cluster.isLiquidatable( + burnRate, + sp.networkFee, + sp.minimumBlocksBeforeLiquidation, + sp.minimumLiquidationCollateral + ) + ) { + revert ClusterNotLiquidatable(); + } + + sp.updateDAOSSV(false, cluster.validatorCount); + + if (cluster.balance != 0) { + balanceLiquidatable = cluster.balance; + cluster.balance = 0; + } + cluster.index = 0; + cluster.networkFeeIndex = 0; + cluster.active = false; + + s.clusters[hashedCluster] = cluster.hashClusterData(); + + if (balanceLiquidatable != 0) { + CoreLib.transferTokenBalance(msg.sender, balanceLiquidatable); + } + + emit ClusterLiquidated(clusterOwner, operatorIds, cluster); // TODO add event to diverge the SSV from ETH clusters + } + + function reactivate( + uint64[] calldata operatorIds, + uint256, // deprecated amount param stays for backward compatability + Cluster memory cluster + ) external payable override { + StorageData storage s = SSVStorage.load(); + + (bytes32 hashedCluster, uint8 version) = cluster.validateHashedCluster(msg.sender, operatorIds, s); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_ETH); if (cluster.active) revert ClusterAlreadyEnabled(); StorageProtocol storage sp = SSVStorageProtocol.load(); @@ -234,7 +294,7 @@ contract SSVClusters is ISSVClusters { sp ); - cluster.balance += amount; + cluster.balance += msg.value; cluster.active = true; cluster.index = clusterIndex; cluster.networkFeeIndex = sp.currentNetworkFeeIndex(); @@ -244,7 +304,7 @@ contract SSVClusters is ISSVClusters { if ( cluster.isLiquidatable( burnRate, - sp.networkFee, + sp.ethNetworkFee, sp.minimumBlocksBeforeLiquidation, sp.minimumLiquidationCollateral ) @@ -252,11 +312,7 @@ contract SSVClusters is ISSVClusters { revert InsufficientBalance(); } - s.clusters[hashedCluster] = cluster.hashClusterData(); - - if (amount > 0) { - CoreLib.deposit(amount); - } + s.ethClusters[hashedCluster] = cluster.hashClusterData(); emit ClusterReactivated(msg.sender, operatorIds, cluster); } @@ -264,26 +320,30 @@ contract SSVClusters is ISSVClusters { function deposit( address clusterOwner, uint64[] calldata operatorIds, - uint256 amount, + uint256, // deprecated amount param stays for backward compatability Cluster memory cluster - ) external override { + ) external payable override { StorageData storage s = SSVStorage.load(); - bytes32 hashedCluster = cluster.validateHashedCluster(clusterOwner, operatorIds, s); - - cluster.balance += amount; + (bytes32 hashedCluster, uint8 version) = cluster.validateHashedCluster(msg.sender, operatorIds, s); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_ETH); - s.clusters[hashedCluster] = cluster.hashClusterData(); + cluster.balance += msg.value; - CoreLib.deposit(amount); + s.ethClusters[hashedCluster] = cluster.hashClusterData(); - emit ClusterDeposited(clusterOwner, operatorIds, amount, cluster); + emit ClusterDeposited(clusterOwner, operatorIds, msg.value, cluster); } - function withdraw(uint64[] calldata operatorIds, uint256 amount, Cluster memory cluster) external override { + function withdraw( + uint64[] calldata operatorIds, + uint256 amount, + Cluster memory cluster + ) external override { StorageData storage s = SSVStorage.load(); - bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s); + (bytes32 hashedCluster, uint8 version) = cluster.validateHashedCluster(msg.sender, operatorIds, s); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_ETH); cluster.validateClusterIsNotLiquidated(); StorageProtocol storage sp = SSVStorageProtocol.load(); @@ -296,10 +356,10 @@ contract SSVClusters is ISSVClusters { for (uint256 i; i < operatorsLength; ++i) { Operator storage operator = SSVStorage.load().operators[operatorIds[i]]; clusterIndex += - operator.snapshot.index + - (uint64(block.number) - operator.snapshot.block) * - operator.fee; - burnRate += operator.fee; + operator.ethSnapshot.index + + (uint64(block.number) - operator.ethSnapshot.block) * + operator.ethFee; + burnRate += operator.ethFee; } } @@ -314,7 +374,7 @@ contract SSVClusters is ISSVClusters { cluster.validatorCount != 0 && cluster.isLiquidatable( burnRate, - sp.networkFee, + sp.ethNetworkFee, sp.minimumBlocksBeforeLiquidation, sp.minimumLiquidationCollateral ) @@ -322,7 +382,7 @@ contract SSVClusters is ISSVClusters { revert InsufficientBalance(); } - s.clusters[hashedCluster] = cluster.hashClusterData(); + s.ethClusters[hashedCluster] = cluster.hashClusterData(); CoreLib.transferBalance(msg.sender, amount); @@ -357,4 +417,51 @@ contract SSVClusters is ISSVClusters { emit ValidatorExited(msg.sender, operatorIds, publicKeys[i]); } } + + function migrateClusterToETH(uint64[] calldata operatorIds, Cluster memory cluster) external payable override { + StorageData storage s = SSVStorage.load(); + StorageProtocol storage sp = SSVStorageProtocol.load(); + + (bytes32 hashedCluster, uint8 version) = cluster.validateHashedCluster(msg.sender, operatorIds, s); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_SSV); + cluster.validateClusterIsNotLiquidated(); + + uint256 ssvBalance = cluster.balance; + + // compute cluster data using ETH fields + (uint64 clusterIndex, uint64 burnRate) = OperatorLib.updateClusterOperators( + operatorIds, + true, + cluster.validatorCount, + s, + sp + ); + + cluster.balance = msg.value; + cluster.active = true; + cluster.index = clusterIndex; + cluster.networkFeeIndex = sp.currentNetworkFeeIndex(); + + sp.updateDAOSSV(false, cluster.validatorCount); + sp.updateDAO(true, cluster.validatorCount); + + if ( + cluster.isLiquidatable( + burnRate, + sp.ethNetworkFee, + sp.minimumBlocksBeforeLiquidation, + sp.minimumLiquidationCollateral + ) + ) { + revert ISSVNetworkCore.InsufficientBalance(); + } + + s.ethClusters[hashedCluster] = cluster.hashClusterData(); + + if (ssvBalance != 0) { + CoreLib.transferTokenBalance(msg.sender, ssvBalance); + } + + emit ClusterMigratedToETH(msg.sender, operatorIds, msg.value, ssvBalance, cluster); + } } diff --git a/contracts/modules/SSVDAO.sol b/contracts/modules/SSVDAO.sol index 08437570..73592c82 100644 --- a/contracts/modules/SSVDAO.sol +++ b/contracts/modules/SSVDAO.sol @@ -17,12 +17,20 @@ contract SSVDAO is ISSVDAO { function updateNetworkFee(uint256 fee) external override { StorageProtocol storage sp = SSVStorageProtocol.load(); - uint64 previousFee = sp.networkFee; + uint64 previousFee = sp.ethNetworkFee; sp.updateNetworkFee(fee); emit NetworkFeeUpdated(previousFee.expand(), fee); } + function updateNetworkFeeSSV(uint256 fee) external override { + StorageProtocol storage sp = SSVStorageProtocol.load(); + uint64 previousFee = sp.networkFee; + + sp.updateNetworkFeeSSV(fee); + emit NetworkFeeUpdated(previousFee.expand(), fee); + } + function withdrawNetworkEarnings(uint256 amount) external override { StorageProtocol storage sp = SSVStorageProtocol.load(); @@ -34,10 +42,29 @@ contract SSVDAO is ISSVDAO { revert InsufficientBalance(); } + sp.ethDaoBalance = networkBalance - shrunkAmount; + sp.ethDaoIndexBlockNumber = uint32(block.number); + + CoreLib.transferBalance(msg.sender, amount); + + emit NetworkEarningsWithdrawn(amount, msg.sender); + } + + function withdrawNetworkSSVEarnings(uint256 amount) external override { + StorageProtocol storage sp = SSVStorageProtocol.load(); + + uint64 shrunkAmount = amount.shrink(); + + uint64 networkBalance = sp.networkTotalEarningsSSV(); + + if (shrunkAmount > networkBalance) { + revert InsufficientBalance(); + } + sp.daoBalance = networkBalance - shrunkAmount; sp.daoIndexBlockNumber = uint32(block.number); - CoreLib.transferBalance(msg.sender, amount); + CoreLib.transferTokenBalance(msg.sender, amount); emit NetworkEarningsWithdrawn(amount, msg.sender); } diff --git a/contracts/modules/SSVOperators.sol b/contracts/modules/SSVOperators.sol index 029cbc34..a2e68d30 100644 --- a/contracts/modules/SSVOperators.sol +++ b/contracts/modules/SSVOperators.sol @@ -12,6 +12,7 @@ import {Counters} from "@openzeppelin/contracts/utils/Counters.sol"; contract SSVOperators is ISSVOperators { uint64 private constant MINIMAL_OPERATOR_FEE = 1_000_000_000; + uint64 private constant MINIMAL_OPERATOR_ETH_FEE = 1_000_000_000; uint64 private constant PRECISION_FACTOR = 10_000; using Types256 for uint256; @@ -28,7 +29,7 @@ contract SSVOperators is ISSVOperators { uint256 fee, bool setPrivate ) external override returns (uint64 id) { - if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) { + if (fee != 0 && fee < MINIMAL_OPERATOR_ETH_FEE) { revert ISSVNetworkCore.FeeTooLow(); } if (fee > SSVStorageProtocol.load().operatorMaxFee) { @@ -43,11 +44,15 @@ contract SSVOperators is ISSVOperators { s.lastOperatorId.increment(); id = uint64(s.lastOperatorId.current()); s.operators[id] = Operator({ + validatorCount: 0, + fee: 0, owner: msg.sender, snapshot: ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}), - validatorCount: 0, - fee: fee.shrink(), - whitelisted: setPrivate + whitelisted: setPrivate, + version: CoreLib.VERSION_ETH, + ethValidatorCount: 0, + ethFee: fee.shrink(), + ethSnapshot: ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}) }); s.operatorsPKs[hashedPk] = id; @@ -64,20 +69,21 @@ contract SSVOperators is ISSVOperators { Operator memory operator = s.operators[operatorId]; operator.checkOwner(); - operator.updateSnapshot(); - uint64 currentBalance = operator.snapshot.balance; + operator.updateSnapshots(); + uint64 currentBalanceETH = operator.ethSnapshot.balance; + uint64 currentBalanceSSV = operator.snapshot.balance; - operator.snapshot.block = 0; - operator.snapshot.balance = 0; - operator.validatorCount = 0; - operator.fee = 0; + operator = _resetOperatorState(operator); s.operators[operatorId] = operator; delete s.operatorsWhitelist[operatorId]; - if (currentBalance > 0) { - _transferOperatorBalanceUnsafe(operatorId, currentBalance.expand()); + if (currentBalanceETH > 0) { + _transferOperatorBalanceUnsafe(operatorId, currentBalanceETH.expand()); + } + if (currentBalanceSSV > 0) { + _transferOperatorTokenBalanceUnsafe(operatorId, currentBalanceSSV.expand()); } emit OperatorRemoved(operatorId); } @@ -85,13 +91,16 @@ contract SSVOperators is ISSVOperators { function declareOperatorFee(uint64 operatorId, uint256 fee) external override { StorageData storage s = SSVStorage.load(); s.operators[operatorId].checkOwner(); + if (s.operators[operatorId].version != CoreLib.VERSION_ETH) { + revert ISSVNetworkCore.IncorrectOperatorVersion(s.operators[operatorId].version); + } StorageProtocol storage sp = SSVStorageProtocol.load(); - if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) revert FeeTooLow(); + if (fee != 0 && fee < MINIMAL_OPERATOR_ETH_FEE) revert FeeTooLow(); if (fee > sp.operatorMaxFee) revert FeeTooHigh(); - uint64 operatorFee = s.operators[operatorId].fee; + uint64 operatorFee = s.operators[operatorId].ethFee; uint64 shrunkFee = fee.shrink(); if (operatorFee == shrunkFee) { @@ -131,7 +140,7 @@ contract SSVOperators is ISSVOperators { if (feeChangeRequest.fee.expand() > SSVStorageProtocol.load().operatorMaxFee) revert FeeTooHigh(); operator.updateSnapshot(); - operator.fee = feeChangeRequest.fee; + operator.ethFee = feeChangeRequest.fee; s.operators[operatorId] = operator; delete s.operatorFeeChangeRequests[operatorId]; @@ -155,13 +164,17 @@ contract SSVOperators is ISSVOperators { Operator memory operator = s.operators[operatorId]; operator.checkOwner(); - if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) revert FeeTooLow(); + if (operator.version != CoreLib.VERSION_ETH) { + revert ISSVNetworkCore.IncorrectOperatorVersion(operator.version); + } + + if (fee != 0 && fee < MINIMAL_OPERATOR_ETH_FEE) revert FeeTooLow(); uint64 shrunkAmount = fee.shrink(); - if (shrunkAmount >= operator.fee) revert FeeIncreaseNotAllowed(); + if (shrunkAmount >= operator.ethFee) revert FeeIncreaseNotAllowed(); operator.updateSnapshot(); - operator.fee = shrunkAmount; + operator.ethFee = shrunkAmount; s.operators[operatorId] = operator; delete s.operatorFeeChangeRequests[operatorId]; @@ -180,41 +193,127 @@ contract SSVOperators is ISSVOperators { } function withdrawOperatorEarnings(uint64 operatorId, uint256 amount) external override { - _withdrawOperatorEarnings(operatorId, amount); + _withdrawOperatorEarnings(operatorId, amount, CoreLib.VERSION_ETH); } function withdrawAllOperatorEarnings(uint64 operatorId) external override { - _withdrawOperatorEarnings(operatorId, 0); + _withdrawOperatorEarnings(operatorId, 0, CoreLib.VERSION_ETH); + } + + function withdrawAllVersionOperatorEarnings(uint64 operatorId) external override { + StorageData storage s = SSVStorage.load(); + Operator memory operator = s.operators[operatorId]; + operator.checkOwner(); + + operator.updateSnapshots(); + + uint64 ethBalance = operator.ethSnapshot.balance; + uint64 ssvBalance = operator.snapshot.balance; + + operator.ethSnapshot.balance = 0; + operator.snapshot.balance = 0; + + s.operators[operatorId] = operator; + + if (ethBalance > 0) { + _transferOperatorBalanceUnsafe(operatorId, ethBalance.expand()); + } + if (ssvBalance > 0) { + _transferOperatorTokenBalanceUnsafe(operatorId, ssvBalance.expand()); + } + } + + function withdrawOperatorEarningsSSV(uint64 operatorId, uint256 amount) external override { + _withdrawOperatorEarnings(operatorId, amount, CoreLib.VERSION_SSV); + } + + function withdrawAllOperatorEarningsSSV(uint64 operatorId) external override { + _withdrawOperatorEarnings(operatorId, 0, CoreLib.VERSION_SSV); + } + + function migrateOperatorToETH(uint64 operatorId) external override { + StorageData storage s = SSVStorage.load(); + Operator memory operator = s.operators[operatorId]; + operator.checkOwner(); + + if (operator.version != CoreLib.VERSION_SSV) { + revert ISSVNetworkCore.IncorrectOperatorVersion(operator.version); + } + + operator.version = CoreLib.VERSION_ETH; + operator.ethFee = OperatorLib.defaultOperatorEthFee(); + if (operator.ethSnapshot.block == 0) { + operator.ethSnapshot = ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}); + } else { + operator.updateSnapshot(); + } + s.operators[operatorId] = operator; + delete s.operatorFeeChangeRequests[operatorId]; } // private functions - function _withdrawOperatorEarnings(uint64 operatorId, uint256 amount) private { + function _withdrawOperatorEarnings(uint64 operatorId, uint256 amount, uint8 version) private { StorageData storage s = SSVStorage.load(); Operator memory operator = s.operators[operatorId]; operator.checkOwner(); - operator.updateSnapshot(); + if (version == CoreLib.VERSION_ETH) { + operator.updateSnapshot(); + } else { + operator.updateSnapshotSSV(); + } uint64 shrunkWithdrawn; uint64 shrunkAmount = amount.shrink(); - if (amount == 0 && operator.snapshot.balance > 0) { - shrunkWithdrawn = operator.snapshot.balance; - } else if (amount > 0 && operator.snapshot.balance >= shrunkAmount) { - shrunkWithdrawn = shrunkAmount; + if (version == CoreLib.VERSION_ETH) { + if (amount == 0 && operator.ethSnapshot.balance > 0) { + shrunkWithdrawn = operator.ethSnapshot.balance; + } else if (amount > 0 && operator.ethSnapshot.balance >= shrunkAmount) { + shrunkWithdrawn = shrunkAmount; + } else { + revert InsufficientBalance(); + } + operator.ethSnapshot.balance -= shrunkWithdrawn; + } else if (version == CoreLib.VERSION_SSV) { + if (amount == 0 && operator.snapshot.balance > 0) { + shrunkWithdrawn = operator.snapshot.balance; + } else if (amount > 0 && operator.snapshot.balance >= shrunkAmount) { + shrunkWithdrawn = shrunkAmount; + } else { + revert InsufficientBalance(); + } + operator.snapshot.balance -= shrunkWithdrawn; } else { - revert InsufficientBalance(); + revert ISSVNetworkCore.IncorrectOperatorVersion(version); } - operator.snapshot.balance -= shrunkWithdrawn; - s.operators[operatorId] = operator; - _transferOperatorBalanceUnsafe(operatorId, shrunkWithdrawn.expand()); + if (version == CoreLib.VERSION_ETH) { + _transferOperatorBalanceUnsafe(operatorId, shrunkWithdrawn.expand()); + } else { + _transferOperatorTokenBalanceUnsafe(operatorId, shrunkWithdrawn.expand()); + } + } + + function _resetOperatorState(Operator memory operator) private pure returns (Operator memory) { + operator.ethSnapshot = ISSVNetworkCore.Snapshot({block: 0, index: 0, balance: 0}); + operator.ethValidatorCount = 0; + operator.ethFee = 0; + operator.snapshot = ISSVNetworkCore.Snapshot({block: 0, index: 0, balance: 0}); + operator.validatorCount = 0; + operator.fee = 0; + return operator; } function _transferOperatorBalanceUnsafe(uint64 operatorId, uint256 amount) private { - CoreLib.transferBalance(msg.sender, amount); + CoreLib.transferBalance(payable(msg.sender), amount); + emit OperatorWithdrawn(msg.sender, operatorId, amount); + } + + function _transferOperatorTokenBalanceUnsafe(uint64 operatorId, uint256 amount) private { + CoreLib.transferTokenBalance(msg.sender, amount); emit OperatorWithdrawn(msg.sender, operatorId, amount); } } diff --git a/contracts/modules/SSVViews.sol b/contracts/modules/SSVViews.sol index 1d50b89c..a9eb1263 100644 --- a/contracts/modules/SSVViews.sol +++ b/contracts/modules/SSVViews.sol @@ -36,6 +36,10 @@ contract SSVViews is ISSVViews { /************************************/ function getOperatorFee(uint64 operatorId) external view override returns (uint256) { + return SSVStorage.load().operators[operatorId].ethFee.expand(); + } + + function getOperatorFeeSSV(uint64 operatorId) external view override returns (uint256) { return SSVStorage.load().operators[operatorId].fee.expand(); } @@ -52,6 +56,31 @@ contract SSVViews is ISSVViews { function getOperatorById( uint64 operatorId + ) + external + view + override + returns ( + address owner, + uint256 ethFee, + uint32 ethValidatorCount, + address whitelistedAddress, + bool isPrivate, + bool isActive + ) + { + ISSVNetworkCore.Operator storage operator = SSVStorage.load().operators[operatorId]; + + owner = operator.owner; + ethFee = operator.ethFee.expand(); + ethValidatorCount = operator.ethValidatorCount; + whitelistedAddress = SSVStorage.load().operatorsWhitelist[operatorId]; + isPrivate = operator.whitelisted; + isActive = operator.ethSnapshot.block != 0; + } + + function getOperatorByIdSSV( + uint64 operatorId ) external view @@ -176,7 +205,41 @@ contract SSVViews is ISSVViews { uint64[] calldata operatorIds, Cluster memory cluster ) external view override returns (bool) { - cluster.validateHashedCluster(clusterOwner, operatorIds, SSVStorage.load()); + (, uint8 version) = cluster.validateHashedCluster(clusterOwner, operatorIds, SSVStorage.load()); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_ETH); + + if (!cluster.active) { + return false; + } + + uint64 clusterIndex; + uint64 burnRate; + uint256 operatorsLength = operatorIds.length; + for (uint256 i; i < operatorsLength; ++i) { + Operator memory operator = SSVStorage.load().operators[operatorIds[i]]; + clusterIndex += operator.ethSnapshot.index + (uint64(block.number) - operator.ethSnapshot.block) * operator.ethFee; + burnRate += operator.ethFee; + } + + StorageProtocol storage sp = SSVStorageProtocol.load(); + + cluster.updateBalance(clusterIndex, sp.currentNetworkFeeIndex()); + return + cluster.isLiquidatable( + burnRate, + sp.ethNetworkFee, + sp.minimumBlocksBeforeLiquidation, + sp.minimumLiquidationCollateral + ); + } + + function isLiquidatableSSV( + address clusterOwner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view override returns (bool) { + (, uint8 version) = cluster.validateHashedCluster(clusterOwner, operatorIds, SSVStorage.load()); + ClusterLib.validateClusterVersion(version, CoreLib.VERSION_SSV); if (!cluster.active) { return false; @@ -193,7 +256,7 @@ contract SSVViews is ISSVViews { StorageProtocol storage sp = SSVStorageProtocol.load(); - cluster.updateBalance(clusterIndex, sp.currentNetworkFeeIndex()); + cluster.updateBalance(clusterIndex, sp.currentNetworkFeeIndexSSV()); return cluster.isLiquidatable( burnRate, @@ -216,7 +279,27 @@ contract SSVViews is ISSVViews { address clusterOwner, uint64[] calldata operatorIds, Cluster memory cluster - ) external view returns (uint256) { + ) external view override returns (uint256) { + cluster.validateHashedCluster(clusterOwner, operatorIds, SSVStorage.load()); + + uint64 aggregateFee; + uint256 operatorsLength = operatorIds.length; + for (uint256 i; i < operatorsLength; ++i) { + Operator memory operator = SSVStorage.load().operators[operatorIds[i]]; + if (operator.owner != address(0)) { + aggregateFee += operator.ethFee; + } + } + + uint64 burnRate = (aggregateFee + SSVStorageProtocol.load().ethNetworkFee) * cluster.validatorCount; + return burnRate.expand(); + } + + function getBurnRateSSV( + address clusterOwner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view override returns (uint256) { cluster.validateHashedCluster(clusterOwner, operatorIds, SSVStorage.load()); uint64 aggregateFee; @@ -240,6 +323,13 @@ contract SSVViews is ISSVViews { Operator memory operator = SSVStorage.load().operators[id]; operator.updateSnapshot(); + return operator.ethSnapshot.balance.expand(); + } + + function getOperatorEarningsSSV(uint64 id) external view override returns (uint256) { + Operator memory operator = SSVStorage.load().operators[id]; + + operator.updateSnapshotSSV(); return operator.snapshot.balance.expand(); } @@ -251,6 +341,31 @@ contract SSVViews is ISSVViews { cluster.validateHashedCluster(clusterOwner, operatorIds, SSVStorage.load()); cluster.validateClusterIsNotLiquidated(); + uint64 clusterIndex; + { + uint256 operatorsLength = operatorIds.length; + for (uint256 i; i < operatorsLength; ++i) { + Operator memory operator = SSVStorage.load().operators[operatorIds[i]]; + clusterIndex += + operator.ethSnapshot.index + + (uint64(block.number) - operator.ethSnapshot.block) * + operator.ethFee; + } + } + + cluster.updateBalance(clusterIndex, SSVStorageProtocol.load().currentNetworkFeeIndex()); + + return cluster.balance; + } + + function getBalanceSSV( + address clusterOwner, + uint64[] calldata operatorIds, + Cluster memory cluster + ) external view override returns (uint256) { + cluster.validateHashedCluster(clusterOwner, operatorIds, SSVStorage.load()); + cluster.validateClusterIsNotLiquidated(); + uint64 clusterIndex; { uint256 operatorsLength = operatorIds.length; @@ -263,16 +378,34 @@ contract SSVViews is ISSVViews { } } - cluster.updateBalance(clusterIndex, SSVStorageProtocol.load().currentNetworkFeeIndex()); + cluster.updateBalance(clusterIndex, SSVStorageProtocol.load().currentNetworkFeeIndexSSV()); return cluster.balance; } + function getClusterVersion(address clusterOwner, uint64[] calldata operatorIds) external view override returns (uint8) { + StorageData storage s = SSVStorage.load(); + bytes32 hashedCluster = keccak256(abi.encodePacked(clusterOwner, operatorIds)); + + if (s.ethClusters[hashedCluster] != bytes32(0)) { + return CoreLib.VERSION_ETH; + } + if (s.clusters[hashedCluster] != bytes32(0)) { + return CoreLib.VERSION_SSV; + } + + revert ClusterDoesNotExists(); + } + /*******************************/ /* DAO External View Functions */ /*******************************/ function getNetworkFee() external view override returns (uint256) { + return SSVStorageProtocol.load().ethNetworkFee.expand(); + } + + function getNetworkFeeSSV() external view override returns (uint256) { return SSVStorageProtocol.load().networkFee.expand(); } @@ -280,6 +413,10 @@ contract SSVViews is ISSVViews { return SSVStorageProtocol.load().networkTotalEarnings().expand(); } + function getNetworkEarningsSSV() external view override returns (uint256) { + return SSVStorageProtocol.load().networkTotalEarningsSSV().expand(); + } + function getOperatorFeeIncreaseLimit() external view override returns (uint64) { return SSVStorageProtocol.load().operatorMaxFeeIncrease; } @@ -305,7 +442,7 @@ contract SSVViews is ISSVViews { } function getNetworkValidatorsCount() external view override returns (uint32) { - return SSVStorageProtocol.load().daoValidatorCount; + return SSVStorageProtocol.load().ethDaoValidatorCount; } function getVersion() external pure override returns (string memory) { diff --git a/contracts/test/SSVNetworkUpgrade.sol b/contracts/test/SSVNetworkUpgrade.sol index b6742a0d..205f13e8 100644 --- a/contracts/test/SSVNetworkUpgrade.sol +++ b/contracts/test/SSVNetworkUpgrade.sol @@ -20,10 +20,12 @@ import {SSVModules} from "../libraries/SSVStorage.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; contract SSVNetworkUpgrade is UUPSUpgradeable, Ownable2StepUpgradeable, + ReentrancyGuardUpgradeable, ISSVNetworkT, ISSVOperators, ISSVClusters, @@ -51,6 +53,7 @@ contract SSVNetworkUpgrade is ) external override initializer onlyProxy { __UUPSUpgradeable_init(); __Ownable_init_unchained(); + __ReentrancyGuard_init(); __SSVNetwork_init_unchained( token_, ssvOperators_, @@ -125,13 +128,20 @@ contract SSVNetworkUpgrade is return abi.decode(result, (uint64)); } - function removeOperator(uint64 operatorId) external override { + function removeOperator(uint64 operatorId) external override nonReentrant { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], abi.encodeWithSignature("removeOperator(uint64)", operatorId) ); } + function migrateOperatorToETH(uint64 operatorId) external override { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], + abi.encodeWithSignature("migrateOperatorToETH(uint64)", operatorId) + ); + } + function declareOperatorFee(uint64 operatorId, uint256 fee) external override { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], @@ -170,28 +180,64 @@ contract SSVNetworkUpgrade is function setOperatorsPrivateUnchecked(uint64[] calldata operatorIds) external override { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], - abi.encodeWithSignature("setOperatorsPrivateUnchecked(address)", operatorIds) + abi.encodeWithSignature("setOperatorsPrivateUnchecked(uint64[])", operatorIds) ); } - function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external { + function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external override { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], - abi.encodeWithSignature("setOperatorsPublicUnchecked(address)", operatorIds) + abi.encodeWithSignature("setOperatorsPublicUnchecked(uint64[])", operatorIds) ); } - function withdrawOperatorEarnings(uint64 operatorId, uint256 amount) external override { + function withdrawOperatorEarnings(uint64 operatorId, uint256 amount) external override nonReentrant { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], abi.encodeWithSignature("withdrawOperatorEarnings(uint64,uint256)", operatorId, amount) ); } - function withdrawAllOperatorEarnings(uint64 operatorId) external override { + function withdrawAllOperatorEarnings(uint64 operatorId) external override nonReentrant { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], + abi.encodeWithSignature("withdrawAllOperatorEarnings(uint64)", operatorId) + ); + } + + function withdrawAllVersionOperatorEarnings(uint64 operatorId) external override nonReentrant { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], - abi.encodeWithSignature("withdrawOperatorEarnings(uint64)", operatorId) + abi.encodeWithSignature("withdrawAllVersionOperatorEarnings(uint64)", operatorId) + ); + } + + function withdrawOperatorEarningsSSV(uint64 operatorId, uint256 amount) external override nonReentrant { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], + abi.encodeWithSignature("withdrawOperatorEarningsSSV(uint64,uint256)", operatorId, amount) + ); + } + + function withdrawAllOperatorEarningsSSV(uint64 operatorId) external override nonReentrant { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS], + abi.encodeWithSignature("withdrawAllOperatorEarningsSSV(uint64)", operatorId) + ); + } + + function migrateClusterToETH(uint64[] calldata operatorIds, ISSVNetworkCore.Cluster memory cluster) + external + payable + override + { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], + abi.encodeWithSignature( + "migrateClusterToETH(uint64[],(uint32,uint64,uint64,bool,uint256))", + operatorIds, + cluster + ) ); } @@ -201,7 +247,7 @@ contract SSVNetworkUpgrade is bytes calldata shares, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external payable override { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], abi.encodeWithSignature( @@ -221,7 +267,7 @@ contract SSVNetworkUpgrade is bytes[] calldata shares, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external payable override { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], abi.encodeWithSignature( @@ -267,7 +313,11 @@ contract SSVNetworkUpgrade is ); } - function liquidate(address owner, uint64[] calldata operatorIds, ISSVNetworkCore.Cluster memory cluster) external { + function liquidate(address owner, uint64[] calldata operatorIds, ISSVNetworkCore.Cluster memory cluster) + external + override + nonReentrant + { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], abi.encodeWithSignature( @@ -279,11 +329,27 @@ contract SSVNetworkUpgrade is ); } + function liquidateSSV(address owner, uint64[] calldata operatorIds, ISSVNetworkCore.Cluster memory cluster) + external + override + nonReentrant + { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], + abi.encodeWithSignature( + "liquidateSSV(address,uint64[],(uint32,uint64,uint64,bool,uint256))", + owner, + operatorIds, + cluster + ) + ); + } + function reactivate( uint64[] calldata operatorIds, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external payable override { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], abi.encodeWithSignature( @@ -300,7 +366,7 @@ contract SSVNetworkUpgrade is uint64[] calldata operatorIds, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external payable override { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], abi.encodeWithSignature( @@ -317,7 +383,7 @@ contract SSVNetworkUpgrade is uint64[] calldata operatorIds, uint256 amount, ISSVNetworkCore.Cluster memory cluster - ) external override { + ) external override nonReentrant { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], abi.encodeWithSignature( @@ -350,13 +416,27 @@ contract SSVNetworkUpgrade is ); } - function withdrawNetworkEarnings(uint256 amount) external override onlyOwner { + function updateNetworkFeeSSV(uint256 fee) external override onlyOwner { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_DAO], + abi.encodeWithSignature("updateNetworkFeeSSV(uint256)", fee) + ); + } + + function withdrawNetworkEarnings(uint256 amount) external override onlyOwner nonReentrant { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_DAO], abi.encodeWithSignature("withdrawNetworkEarnings(uint256)", amount) ); } + function withdrawNetworkSSVEarnings(uint256 amount) external override onlyOwner nonReentrant { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_DAO], + abi.encodeWithSignature("withdrawNetworkSSVEarnings(uint256)", amount) + ); + } + function updateOperatorFeeIncreaseLimit(uint64 percentage) external override onlyOwner { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_DAO], diff --git a/contracts/test/mocks/FakeWhitelistingContract.sol b/contracts/test/mocks/FakeWhitelistingContract.sol index 9db27c3d..8ccaaee6 100644 --- a/contracts/test/mocks/FakeWhitelistingContract.sol +++ b/contracts/test/mocks/FakeWhitelistingContract.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.24; import "../../interfaces/external/ISSVWhitelistingContract.sol"; import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -/// @notice Whitelisted contract that passes the validatity check of supporting ISSVWhitelistingContract +/// @notice Whitelisted contract that passes the validation check of supporting ISSVWhitelistingContract /// and tries to re-enter SSVNetwork.registerValidator function. contract FakeWhitelistingContract is ERC165 { struct Cluster { @@ -32,13 +32,13 @@ contract FakeWhitelistingContract is ERC165 { uint64[] memory _operatorIds, bytes calldata _sharesData, uint256 _amount, - Cluster memory _cluserData + Cluster memory _clusterData ) external { publicKey = _publicKey; operatorIds = _operatorIds; sharesData = _sharesData; amount = _amount; - clusterData = _cluserData; + clusterData = _clusterData; } function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { diff --git a/contracts/test/mocks/GenericWhitelistContract.sol b/contracts/test/mocks/GenericWhitelistContract.sol index 987b0cfc..7a924f2f 100644 --- a/contracts/test/mocks/GenericWhitelistContract.sol +++ b/contracts/test/mocks/GenericWhitelistContract.sol @@ -19,9 +19,9 @@ contract GenericWhitelistContract { uint64[] memory _operatorIds, bytes calldata _sharesData, uint256 _amount, - ISSVNetworkCore.Cluster memory _cluserData + ISSVNetworkCore.Cluster memory _clusterData ) external { ssvToken.approve(address(ssvContract), _amount); - ssvContract.registerValidator(_publicKey, _operatorIds, _sharesData, _amount, _cluserData); + ssvContract.registerValidator(_publicKey, _operatorIds, _sharesData, _amount, _clusterData); } } diff --git a/contracts/test/modules/SSVOperatorsUpdate.sol b/contracts/test/modules/SSVOperatorsUpdate.sol index d8f3d99f..3e4cd23c 100644 --- a/contracts/test/modules/SSVOperatorsUpdate.sol +++ b/contracts/test/modules/SSVOperatorsUpdate.sol @@ -43,7 +43,11 @@ contract SSVOperatorsUpdate is ISSVOperators { snapshot: ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}), validatorCount: 0, fee: fee.shrink(), - whitelisted: setPrivate + whitelisted: setPrivate, + version: CoreLib.VERSION_ETH, + ethValidatorCount: 0, + ethFee: 0, + ethSnapshot: ISSVNetworkCore.Snapshot({block: 0, index: 0, balance: 0}) }); s.operatorsPKs[hashedPk] = id; @@ -59,33 +63,89 @@ contract SSVOperatorsUpdate is ISSVOperators { Operator memory operator = s.operators[operatorId]; operator.checkOwner(); - operator.updateSnapshot(); - uint64 currentBalance = operator.snapshot.balance; + operator.updateSnapshots(); + uint64 currentBalanceETH = operator.ethSnapshot.balance; + uint64 currentBalanceSSV = operator.snapshot.balance; - operator.snapshot.block = 0; - operator.snapshot.balance = 0; - operator.validatorCount = 0; - operator.fee = 0; + operator = _resetOperatorState(operator); s.operators[operatorId] = operator; - if (s.operatorsWhitelist[operatorId] != address(0)) { - delete s.operatorsWhitelist[operatorId]; - } + delete s.operatorsWhitelist[operatorId]; - if (currentBalance > 0) { - _transferOperatorBalanceUnsafe(operatorId, currentBalance.expand()); + if (currentBalanceETH > 0) { + _transferOperatorBalanceUnsafe(operatorId, currentBalanceETH.expand()); + } + if (currentBalanceSSV > 0) { + _transferOperatorTokenBalanceUnsafe(operatorId, currentBalanceSSV.expand()); } emit OperatorRemoved(operatorId); } - function declareOperatorFee(uint64 operatorId, uint256 fee) external override {} - - function executeOperatorFee(uint64 operatorId) external override { + function migrateOperatorToETH(uint64 operatorId) external override { StorageData storage s = SSVStorage.load(); Operator memory operator = s.operators[operatorId]; operator.checkOwner(); + if (operator.version != CoreLib.VERSION_SSV) { + revert ISSVNetworkCore.IncorrectOperatorVersion(operator.version); + } + + uint64 targetFee = operator.ethFee == 0 ? OperatorLib.defaultOperatorEthFee() : operator.ethFee; + if (targetFee > SSVStorageProtocol.load().operatorMaxFee) revert ISSVNetworkCore.FeeTooHigh(); + + operator.version = CoreLib.VERSION_ETH; + operator.ethFee = targetFee; + if (operator.ethSnapshot.block == 0) { + operator.ethSnapshot = ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}); + } else { + operator.updateSnapshot(); + } + + s.operators[operatorId] = operator; + delete s.operatorFeeChangeRequests[operatorId]; + } + + function declareOperatorFee(uint64 operatorId, uint256 fee) external override { + StorageData storage s = SSVStorage.load(); + Operator storage operator = s.operators[operatorId]; + operator.checkOwner(); + if (operator.version != CoreLib.VERSION_ETH && operator.version != CoreLib.VERSION_SSV) { + revert ISSVNetworkCore.IncorrectOperatorVersion(operator.version); + } + + StorageProtocol storage sp = SSVStorageProtocol.load(); + + if (fee != 0 && fee < MINIMAL_OPERATOR_FEE) revert FeeTooLow(); + if (fee > sp.operatorMaxFee) revert FeeTooHigh(); + + uint64 operatorFee = operator.fee; + uint64 shrunkFee = fee.shrink(); + + if (operatorFee == shrunkFee) { + revert SameFeeChangeNotAllowed(); + } else if (shrunkFee != 0 && operatorFee == 0) { + revert FeeIncreaseNotAllowed(); + } + + // @dev 100% = 10000, 10% = 1000 - using 10000 to represent 2 digit precision + uint64 maxAllowedFee = (operatorFee * (PRECISION_FACTOR + sp.operatorMaxFeeIncrease)) / PRECISION_FACTOR; + + if (shrunkFee > maxAllowedFee) revert FeeExceedsIncreaseLimit(); + + s.operatorFeeChangeRequests[operatorId] = OperatorFeeChangeRequest( + shrunkFee, + uint64(block.timestamp) + sp.declareOperatorFeePeriod, + uint64(block.timestamp) + sp.declareOperatorFeePeriod + sp.executeOperatorFeePeriod + ); + emit OperatorFeeDeclared(msg.sender, operatorId, block.number, fee); + } + + function executeOperatorFee(uint64 operatorId) external override { + StorageData storage s = SSVStorage.load(); + Operator storage operator = s.operators[operatorId]; + if (operator.owner != msg.sender) revert ISSVNetworkCore.CallerNotOwnerWithData(msg.sender, operator.owner); + OperatorFeeChangeRequest memory feeChangeRequest = s.operatorFeeChangeRequests[operatorId]; if (feeChangeRequest.approvalBeginTime == 0) revert NoFeeDeclared(); @@ -96,9 +156,19 @@ contract SSVOperatorsUpdate is ISSVOperators { revert ApprovalNotWithinTimeframe(); } - operator.updateSnapshot(); - operator.fee = feeChangeRequest.fee; - s.operators[operatorId] = operator; + if (operator.version == CoreLib.VERSION_ETH) { + operator.updateSnapshotSt(); + operator.ethFee = feeChangeRequest.fee; + } else if (operator.version == CoreLib.VERSION_SSV) { + operator.updateSnapshotStSVV(); + operator.version = CoreLib.VERSION_ETH; + operator.ethFee = feeChangeRequest.fee; + operator.ethValidatorCount = 0; + operator.ethSnapshot = ISSVNetworkCore.Snapshot({block: uint32(block.number), index: 0, balance: 0}); + operator.fee = 0; + } else { + revert ISSVNetworkCore.IncorrectOperatorVersion(operator.version); + } delete s.operatorFeeChangeRequests[operatorId]; @@ -143,41 +213,109 @@ contract SSVOperatorsUpdate is ISSVOperators { } function withdrawOperatorEarnings(uint64 operatorId, uint256 amount) external override { - _withdrawOperatorEarnings(operatorId, amount); + _withdrawOperatorEarnings(operatorId, amount, CoreLib.VERSION_ETH); } function withdrawAllOperatorEarnings(uint64 operatorId) external override { - _withdrawOperatorEarnings(operatorId, 0); + _withdrawOperatorEarnings(operatorId, 0, CoreLib.VERSION_ETH); + } + + function withdrawAllVersionOperatorEarnings(uint64 operatorId) external override { + StorageData storage s = SSVStorage.load(); + Operator memory operator = s.operators[operatorId]; + operator.checkOwner(); + + operator.updateSnapshots(); + + uint64 ethBalance = operator.ethSnapshot.balance; + uint64 ssvBalance = operator.snapshot.balance; + + operator.ethSnapshot.balance = 0; + operator.snapshot.balance = 0; + + s.operators[operatorId] = operator; + + if (ethBalance > 0) { + _transferOperatorBalanceUnsafe(operatorId, ethBalance.expand()); + } + if (ssvBalance > 0) { + _transferOperatorTokenBalanceUnsafe(operatorId, ssvBalance.expand()); + } + } + + function withdrawOperatorEarningsSSV(uint64 operatorId, uint256 amount) external override { + _withdrawOperatorEarnings(operatorId, amount, CoreLib.VERSION_SSV); + } + + function withdrawAllOperatorEarningsSSV(uint64 operatorId) external override { + _withdrawOperatorEarnings(operatorId, 0, CoreLib.VERSION_SSV); } // private functions - function _withdrawOperatorEarnings(uint64 operatorId, uint256 amount) private { + function _withdrawOperatorEarnings(uint64 operatorId, uint256 amount, uint8 expectedVersion) private { StorageData storage s = SSVStorage.load(); Operator memory operator = s.operators[operatorId]; operator.checkOwner(); - operator.updateSnapshot(); + if (expectedVersion == CoreLib.VERSION_ETH) { + operator.updateSnapshot(); + } else { + operator.updateSnapshotSSV(); + } uint64 shrunkWithdrawn; uint64 shrunkAmount = amount.shrink(); - if (amount == 0 && operator.snapshot.balance > 0) { - shrunkWithdrawn = operator.snapshot.balance; - } else if (amount > 0 && operator.snapshot.balance >= shrunkAmount) { - shrunkWithdrawn = shrunkAmount; + if (expectedVersion == CoreLib.VERSION_ETH) { + if (amount == 0 && operator.ethSnapshot.balance > 0) { + shrunkWithdrawn = operator.ethSnapshot.balance; + } else if (amount > 0 && operator.ethSnapshot.balance >= shrunkAmount) { + shrunkWithdrawn = shrunkAmount; + } else { + revert InsufficientBalance(); + } + operator.ethSnapshot.balance -= shrunkWithdrawn; + } else if (expectedVersion == CoreLib.VERSION_SSV) { + if (amount == 0 && operator.snapshot.balance > 0) { + shrunkWithdrawn = operator.snapshot.balance; + } else if (amount > 0 && operator.snapshot.balance >= shrunkAmount) { + shrunkWithdrawn = shrunkAmount; + } else { + revert InsufficientBalance(); + } + operator.snapshot.balance -= shrunkWithdrawn; } else { - revert InsufficientBalance(); + revert ISSVNetworkCore.IncorrectOperatorVersion(expectedVersion); } - operator.snapshot.balance -= shrunkWithdrawn; - s.operators[operatorId] = operator; - _transferOperatorBalanceUnsafe(operatorId, shrunkWithdrawn.expand()); + if (expectedVersion == CoreLib.VERSION_ETH) { + _transferOperatorBalanceUnsafe(operatorId, shrunkWithdrawn.expand()); + } else if (expectedVersion == CoreLib.VERSION_SSV) { + _transferOperatorTokenBalanceUnsafe(operatorId, shrunkWithdrawn.expand()); + } else { + revert ISSVNetworkCore.IncorrectOperatorVersion(expectedVersion); + } + } + + function _resetOperatorState(Operator memory operator) private pure returns (Operator memory) { + operator.ethSnapshot = ISSVNetworkCore.Snapshot({block: 0, index: 0, balance: 0}); + operator.ethValidatorCount = 0; + operator.ethFee = 0; + operator.snapshot = ISSVNetworkCore.Snapshot({block: 0, index: 0, balance: 0}); + operator.validatorCount = 0; + operator.fee = 0; + return operator; } function _transferOperatorBalanceUnsafe(uint64 operatorId, uint256 amount) private { CoreLib.transferBalance(msg.sender, amount); emit OperatorWithdrawn(msg.sender, operatorId, amount); } + + function _transferOperatorTokenBalanceUnsafe(uint64 operatorId, uint256 amount) private { + CoreLib.transferTokenBalance(msg.sender, amount); + emit OperatorWithdrawn(msg.sender, operatorId, amount); + } } diff --git a/package-lock.json b/package-lock.json index 824e30f7..2999c27c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,8 +28,7 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@aws-crypto/crc32": { "version": "5.2.0", @@ -1088,7 +1087,6 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1954,7 +1952,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1963,15 +1960,13 @@ "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1982,7 +1977,6 @@ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, - "peer": true, "dependencies": { "@noble/hashes": "1.3.2" }, @@ -1995,7 +1989,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "dev": true, - "peer": true, "engines": { "node": ">= 16" }, @@ -2156,6 +2149,7 @@ "integrity": "sha512-7xEaz2X8p47qWIAqtV2z03MmusheHm8bvY2mDlxo9JiT2BgSx59GSdv5+mzwOvsuKDbTij7oqDnwFyYOlHREEQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.1.1", "lodash.isequal": "^4.5.0" @@ -2285,7 +2279,6 @@ "integrity": "sha512-BRgNaApHTdmk0NNTVYMltRXUFQGaWKHKnaaOyp9TG/BsUUkW3mH1ds5+rM4UBUIHivIyh3fKFDCOGJIJcQG9aw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ethersproject/address": "5.6.1", "@nomicfoundation/solidity-analyzer": "^0.1.1", @@ -2313,7 +2306,6 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, "dependencies": { "@ethersproject/bignumber": "^5.6.2", "@ethersproject/bytes": "^5.6.1", @@ -2327,7 +2319,6 @@ "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", "dev": true, - "peer": true, "dependencies": { "nofilter": "^3.1.0" }, @@ -2340,8 +2331,7 @@ "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-ui/-/ignition-ui-0.15.13.tgz", "integrity": "sha512-HbTszdN1iDHCkUS9hLeooqnLEW2U45FaqFwFEYT8nIno2prFZhG+n68JEERjmfFCB5u0WgbuJwk3CgLoqtSL7Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@nomicfoundation/slang": { "version": "0.18.3", @@ -3807,7 +3797,6 @@ "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", "dev": true, - "peer": true, "dependencies": { "antlr4ts": "^0.5.0-alpha.4" } @@ -3829,29 +3818,25 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/bn.js": { "version": "5.1.5", @@ -3879,13 +3864,15 @@ "version": "4.3.16", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz", "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/chai-as-promised": { "version": "7.1.8", "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz", "integrity": "sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==", "dev": true, + "peer": true, "dependencies": { "@types/chai": "*" } @@ -3895,7 +3882,6 @@ "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*" } @@ -3911,7 +3897,6 @@ "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", "dev": true, - "peer": true, "dependencies": { "@types/node": "*" } @@ -3962,6 +3947,7 @@ "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -3979,8 +3965,7 @@ "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/responselike": { "version": "1.0.3", @@ -4039,7 +4024,6 @@ "url": "https://github.com/sponsors/wagmi-dev" } ], - "peer": true, "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" @@ -4072,7 +4056,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4085,7 +4068,6 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", "dev": true, - "peer": true, "dependencies": { "acorn": "^8.11.0" }, @@ -4106,8 +4088,7 @@ "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/agent-base": { "version": "6.0.2", @@ -4139,7 +4120,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", @@ -4260,8 +4240,7 @@ "version": "0.5.0-alpha.4", "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/anymatch": { "version": "3.1.3", @@ -4280,8 +4259,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/argparse": { "version": "2.0.1", @@ -4310,7 +4288,6 @@ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4319,8 +4296,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/asn1": { "version": "0.2.6", @@ -4379,7 +4355,6 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, - "peer": true, "engines": { "node": "*" } @@ -4389,7 +4364,6 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -4901,6 +4875,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -5054,7 +5029,6 @@ "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", "dev": true, - "peer": true, "dependencies": { "nofilter": "^3.1.0" }, @@ -5114,7 +5088,6 @@ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", "dev": true, - "peer": true, "engines": { "node": "*" } @@ -5392,7 +5365,6 @@ "engines": [ "node >= 0.8" ], - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -5405,7 +5377,6 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5420,15 +5391,13 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "peer": true + "dev": true }, "node_modules/concat-stream/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -5573,15 +5542,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/crypt": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", "dev": true, - "peer": true, "engines": { "node": "*" } @@ -6228,7 +6195,6 @@ "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz", "integrity": "sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw==", "dev": true, - "peer": true, "dependencies": { "@solidity-parser/parser": "^0.14.0", "axios": "^1.5.1", @@ -6263,8 +6229,7 @@ "type": "individual", "url": "https://paulmillr.com/funding/" } - ], - "peer": true + ] }, "node_modules/eth-gas-reporter/node_modules/@scure/bip32": { "version": "1.1.5", @@ -6277,7 +6242,6 @@ "url": "https://paulmillr.com/funding/" } ], - "peer": true, "dependencies": { "@noble/hashes": "~1.2.0", "@noble/secp256k1": "~1.7.0", @@ -6295,7 +6259,6 @@ "url": "https://paulmillr.com/funding/" } ], - "peer": true, "dependencies": { "@noble/hashes": "~1.2.0", "@scure/base": "~1.1.0" @@ -6306,7 +6269,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -6316,7 +6278,6 @@ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "dev": true, - "peer": true, "dependencies": { "object-assign": "^4.1.0", "string-width": "^2.1.1" @@ -6333,7 +6294,6 @@ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", "dev": true, - "peer": true, "dependencies": { "@noble/hashes": "1.2.0", "@noble/secp256k1": "1.7.1", @@ -6357,7 +6317,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@ethersproject/abi": "5.8.0", "@ethersproject/abstract-provider": "5.8.0", @@ -6396,7 +6355,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -6406,7 +6364,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, - "peer": true, "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -6420,7 +6377,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^3.0.0" }, @@ -6622,8 +6578,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/ethjs-unit": { "version": "0.1.6", @@ -7033,7 +6988,6 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -7057,8 +7011,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -7137,7 +7090,6 @@ "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -7490,6 +7442,7 @@ "integrity": "sha512-du7ecjx1/ueAUjvtZhVkJvWytPCjlagG3ZktYTphfzAbc1Flc6sRolw5mhKL/Loub1EIFRaflutM4bdB/YsUUw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ethereumjs/util": "^9.1.0", "@ethersproject/abi": "^5.1.2", @@ -8089,7 +8042,6 @@ "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", "dev": true, - "peer": true, "dependencies": { "caseless": "^0.12.0", "concat-stream": "^1.6.2", @@ -8135,7 +8087,6 @@ "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", "dev": true, - "peer": true, "dependencies": { "@types/node": "^10.0.3" } @@ -8144,8 +8095,7 @@ "version": "10.17.60", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/http-signature": { "version": "1.2.0", @@ -8271,7 +8221,6 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz", "integrity": "sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==", "dev": true, - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -8548,7 +8497,6 @@ "url": "https://github.com/sponsors/wagmi-dev" } ], - "peer": true, "peerDependencies": { "ws": "*" } @@ -8629,8 +8577,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true }, "node_modules/json-stream-stringify": { "version": "3.1.6", @@ -8654,7 +8601,6 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -8667,7 +8613,6 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -8789,8 +8734,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -8802,15 +8746,13 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "peer": true + "dev": true }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -8833,7 +8775,6 @@ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, - "peer": true, "dependencies": { "get-func-name": "^2.0.1" } @@ -8858,15 +8799,13 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/markdown-table": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -9414,7 +9353,6 @@ "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz", "integrity": "sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==", "dev": true, - "peer": true, "dependencies": { "json-stringify-safe": "^5.0.1", "minimist": "^1.2.5", @@ -9790,8 +9728,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/parse-headers": { "version": "2.0.6", @@ -9865,7 +9802,6 @@ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, - "peer": true, "engines": { "node": "*" } @@ -9971,7 +9907,6 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "dev": true, - "peer": true, "dependencies": { "asap": "~2.0.6" } @@ -10250,7 +10185,6 @@ "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", "dev": true, - "peer": true, "dependencies": { "req-from": "^2.0.0" }, @@ -10263,7 +10197,6 @@ "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", "dev": true, - "peer": true, "dependencies": { "resolve-from": "^3.0.0" }, @@ -10354,7 +10287,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10383,7 +10315,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -10805,7 +10736,6 @@ "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", "dev": true, - "peer": true, "dependencies": { "charenc": ">= 0.0.1", "crypt": ">= 0.0.1" @@ -10909,7 +10839,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -10965,6 +10894,7 @@ "integrity": "sha512-qKqgm8TPpcnCK0HCDLJrjbOA2tQNEJY4dHX/LSSQ9iwYFS973MwjtgYn2Iv3vfCEQJTj5xtm4cuUMzlJsJSMbg==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "@ethersproject/abi": "^5.0.9", "@solidity-parser/parser": "^0.20.1", @@ -11152,7 +11082,6 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", "dev": true, - "peer": true, "dependencies": { "readable-stream": "^3.0.0" } @@ -11748,7 +11677,6 @@ "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", "dev": true, - "peer": true, "dependencies": { "http-response-object": "^3.0.1", "sync-rpc": "^1.2.1", @@ -11763,7 +11691,6 @@ "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", "dev": true, - "peer": true, "dependencies": { "get-port": "^3.1.0" } @@ -11773,7 +11700,6 @@ "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", "dev": true, - "peer": true, "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -11809,7 +11735,6 @@ "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", "dev": true, - "peer": true, "dependencies": { "@types/concat-stream": "^1.6.0", "@types/form-data": "0.0.33", @@ -11831,8 +11756,7 @@ "version": "8.10.66", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/then-request/node_modules/form-data": { "version": "2.5.5", @@ -11840,7 +11764,6 @@ "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -11858,7 +11781,6 @@ "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", "dev": true, - "peer": true, "dependencies": { "readable-stream": "3" } @@ -11914,6 +11836,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12056,7 +11979,6 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, - "peer": true, "engines": { "node": ">=0.3.1" } @@ -12345,8 +12267,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", @@ -12430,7 +12351,6 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -12480,6 +12400,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -12535,8 +12456,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/validator": { "version": "13.15.23", @@ -12615,15 +12535,13 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==", - "dev": true, - "peer": true + "dev": true }, "node_modules/viem/node_modules/@noble/curves": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", "dev": true, - "peer": true, "dependencies": { "@noble/hashes": "1.4.0" }, @@ -12636,7 +12554,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, - "peer": true, "engines": { "node": ">= 16" }, @@ -12649,7 +12566,6 @@ "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.5.tgz", "integrity": "sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/wevm" }, @@ -13751,7 +13667,6 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, - "peer": true, "engines": { "node": ">=6" }