diff --git a/.env.example b/.env.example index f213c7b2..2538a90d 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ GOERLI_ETH_NODE_URL= GOERLI_OWNER_PRIVATE_KEY= +HOLESKY_ETH_NODE_URL= +HOLESKY_OWNER_PRIVATE_KEY= MAINNET_ETH_NODE_URL= MAINNET_OWNER_PRIVATE_KEY= GAS_PRICE= diff --git a/.openzeppelin/goerli.json b/.openzeppelin/goerli.json index 80a1047a..59b7ac36 100644 --- a/.openzeppelin/goerli.json +++ b/.openzeppelin/goerli.json @@ -130,6 +130,11 @@ "address": "0xcDc4423E9ffa9542d4CdDf42a70859C84859d2A9", "txHash": "0x964e728e77bd4afa121c93bfd55076c36a5de0b764214dbb9ee574fa1976a9ad", "kind": "uups" + }, + { + "address": "0xFe35A31e57946E8aadd25158BdF303A36dEf3332", + "txHash": "0x73d4d1df08c7c3d95e8b34545aa55b9ceb7e7e07f7138cb524c7565c56b03e91", + "kind": "uups" } ], "impls": { @@ -6888,6 +6893,458 @@ } } } + }, + "0b47d2abd2279e76837ff94805e14a88d019593eed05755759cc40256af16f0e": { + "address": "0x296C821446f8756A6d30784C6CF63B65c2B82863", + "txHash": "0xc449b9dd299fe868cc5e284843897df96fb79db91b313450c96ee7e6c3b80a69", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_pendingOwner", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:27" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:70" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "368acb5d5852996f01c66abefb8ca54bce00f1977c8aaed5efcf0c75f250303d": { + "address": "0xE20E557C5173D505a58eEBf3C4E6aD2672c57Fd1", + "txHash": "0xcde725501175a0ce2045f30df0fc6b3ed3fed0f2baab220f25163ec7c4f93933", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_pendingOwner", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:27" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:70" + }, + { + "label": "ssvNetwork", + "offset": 0, + "slot": "251", + "type": "t_contract(ISSVViews)4189", + "contract": "SSVNetworkViews", + "src": "contracts/SSVNetworkViews.sol:19" + }, + { + "label": "__gap", + "offset": 0, + "slot": "252", + "type": "t_array(t_uint256)50_storage", + "contract": "SSVNetworkViews", + "src": "contracts/SSVNetworkViews.sol:23" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(ISSVViews)4189": { + "label": "contract ISSVViews", + "numberOfBytes": "20" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "1e550124c6e28f236ee8632b9abc9a68dac2f537aff421981b339520c87ad539": { + "address": "0x0097bBea812414d42D2AD6d76c7da1c794AA16A9", + "txHash": "0xf3a3e1c1742cd1d11b7271abf2768c6046122eb06a2f66971a931021b25763c5", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_pendingOwner", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:27" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:70" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "d876760172e5575262f8fe267daaa9f5045a6c995da21c74c61e769c00d0cb79": { + "address": "0x5fBf3Fe05112DE129Bf26dB70B03630Bc9A7233a", + "txHash": "0xa7cdfbbb6c6085cf5a574d3c1a93fbbb270987c566a33bcaa9f305d29347b2ea", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_pendingOwner", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:27" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:70" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } } } } diff --git a/.openzeppelin/unknown-17000.json b/.openzeppelin/unknown-17000.json new file mode 100644 index 00000000..e65cceda --- /dev/null +++ b/.openzeppelin/unknown-17000.json @@ -0,0 +1,263 @@ +{ + "manifestVersion": "3.2", + "proxies": [ + { + "address": "0x0d33801785340072C452b994496B19f196b7eE15", + "txHash": "0x9c6605dab4a2d23e480bf90f476b34498bd6d7a4e3c232835544bd8c94971427", + "kind": "uups" + }, + { + "address": "0x656d5cC4e7d49EaCC063cBB8D3e072F2841D68b4", + "txHash": "0x05fec282860f01d95aa19fea01907f0e377d529685c33da59dd0b7c85f96b0b2", + "kind": "uups" + }, + { + "address": "0x38A4794cCEd47d3baf7370CcC43B560D3a1beEFA", + "txHash": "0x998c38ff37b47e69e23c21a8079168b7e0e0ade7244781587b00be3f08a725c6", + "kind": "uups" + }, + { + "address": "0x352A18AEe90cdcd825d1E37d9939dCA86C00e281", + "txHash": "0xf36e0e114031e961a1e3452477ed71658cf0f809be94832b4dc4a99a293ef664", + "kind": "uups" + } + ], + "impls": { + "d876760172e5575262f8fe267daaa9f5045a6c995da21c74c61e769c00d0cb79": { + "address": "0x116522F4D00b42Efce0aA77f7ddAd1d27705F36b", + "txHash": "0xd04d7ebb1f3211c5006d965f1c3762c866e8eb77027428ab8989904b4af28a16", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_pendingOwner", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:27" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:70" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + }, + "368acb5d5852996f01c66abefb8ca54bce00f1977c8aaed5efcf0c75f250303d": { + "address": "0xa9d0096bdbf97401F1B5E8D5330Ee8b7f0cb975D", + "txHash": "0x6fbd987b485c9f50a953b1ad38de6bcca366fbe9f9cb1ae88d81aa9b085fb3c6", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "ERC1967UpgradeUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/ERC1967/ERC1967UpgradeUpgradeable.sol:169" + }, + { + "label": "__gap", + "offset": 0, + "slot": "51", + "type": "t_array(t_uint256)50_storage", + "contract": "UUPSUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol:111" + }, + { + "label": "__gap", + "offset": 0, + "slot": "101", + "type": "t_array(t_uint256)50_storage", + "contract": "ContextUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36" + }, + { + "label": "_owner", + "offset": 0, + "slot": "151", + "type": "t_address", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:22" + }, + { + "label": "__gap", + "offset": 0, + "slot": "152", + "type": "t_array(t_uint256)49_storage", + "contract": "OwnableUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol:94" + }, + { + "label": "_pendingOwner", + "offset": 0, + "slot": "201", + "type": "t_address", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:27" + }, + { + "label": "__gap", + "offset": 0, + "slot": "202", + "type": "t_array(t_uint256)49_storage", + "contract": "Ownable2StepUpgradeable", + "src": "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol:70" + }, + { + "label": "ssvNetwork", + "offset": 0, + "slot": "251", + "type": "t_contract(ISSVViews)2185", + "contract": "SSVNetworkViews", + "src": "contracts/SSVNetworkViews.sol:19" + }, + { + "label": "private__gap", + "offset": 0, + "slot": "252", + "type": "t_array(t_uint256)50_storage", + "contract": "SSVNetworkViews", + "src": "contracts/SSVNetworkViews.sol:23" + } + ], + "types": { + "t_address": { + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]", + "numberOfBytes": "1568" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(ISSVViews)2185": { + "label": "contract ISSVViews", + "numberOfBytes": "20" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + } + } + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..925d077a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# Changelog + +All notable changes to SSV Network contracts will be documented in this file. + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [v1.0.0.rc5] - 2023-10-02 + +### Fixed + +- [22d2859](https://github.com/bloxapp/ssv-network/pull/262/commits/22d2859d8fe6267b09c7a1c9c645df19bdaa03ff) Fix bug in network earnings withdrawals. +- [d25d188](https://github.com/bloxapp/ssv-network/pull/265/commits/d25d18886459e631fb4453df7a47db19982ec80e) Fix Types.shrink() bug. + +### Added +- [bf0c51d](https://github.com/bloxapp/ssv-network/pull/263/commits/bf0c51d4df191018052d11425c9fcc252de61431) A validator can voluntarily exit. + + +## [v1.0.0.rc4] - 2023-08-31 + +- Audit fixes/recommendations +- Validate a cluster with 0 validators can not be liquidated +- Deployment process now uses hardhat tasks +- The DAO can set a maximum operator fee (SSV) +- Remove the setRegisterAuth function (register operator/validator without restrictions) +- SSVNetworkViews contract does not throw an error as a way of return. diff --git a/contracts/SSVNetwork.sol b/contracts/SSVNetwork.sol index e445fee6..3380af0a 100644 --- a/contracts/SSVNetwork.sol +++ b/contracts/SSVNetwork.sol @@ -12,7 +12,6 @@ import "./libraries/Types.sol"; import "./libraries/CoreLib.sol"; import "./libraries/SSVStorage.sol"; import "./libraries/SSVStorageProtocol.sol"; -import "./libraries/RegisterAuth.sol"; import "./SSVProxy.sol"; @@ -119,8 +118,6 @@ contract SSVNetwork is /*******************************/ function registerOperator(bytes calldata publicKey, uint256 fee) external override returns (uint64 id) { - if (!RegisterAuth.load().authorization[msg.sender].registerOperator) revert NotAuthorized(); - _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]); } @@ -175,8 +172,6 @@ contract SSVNetwork is uint256 amount, ISSVNetworkCore.Cluster memory cluster ) external override { - if (!RegisterAuth.load().authorization[msg.sender].registerValidator) revert NotAuthorized(); - _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); } @@ -221,6 +216,10 @@ contract SSVNetwork is _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); } + function exitValidator(bytes calldata publicKey, uint64[] calldata operatorIds) external override { + _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS]); + } + function updateNetworkFee(uint256 fee) external override onlyOwner { _delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_DAO]); } @@ -263,18 +262,4 @@ contract SSVNetwork is function updateModule(SSVModules moduleId, address moduleAddress) external onlyOwner { CoreLib.setModuleContract(moduleId, moduleAddress); } - - /*******************************/ - /* Register Authorization */ - /*******************************/ - function setRegisterAuth(address userAddress, bool authOperator, bool authValidator) external override onlyOwner { - RegisterAuth.load().authorization[userAddress] = Authorization(authOperator, authValidator); - } - - function getRegisterAuth( - address userAddress - ) external view override returns (bool authOperators, bool authValidators) { - Authorization memory auth = RegisterAuth.load().authorization[userAddress]; - return (auth.registerOperator, auth.registerValidator); - } } diff --git a/contracts/SSVNetworkViews.sol b/contracts/SSVNetworkViews.sol index b731d7f8..5da979a6 100644 --- a/contracts/SSVNetworkViews.sol +++ b/contracts/SSVNetworkViews.sol @@ -39,7 +39,7 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews /* Validator External View Functions */ /*************************************/ - function getValidator(address clusterOwner, bytes calldata publicKey) external view override returns (bool active) { + function getValidator(address clusterOwner, bytes calldata publicKey) external view override returns (bool) { return ssvNetwork.getValidator(clusterOwner, publicKey); } @@ -55,7 +55,9 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getOperatorDeclaredFee(operatorId); } - function getOperatorById(uint64 operatorId) external view override returns (address, uint256, uint32, address, bool, bool) { + function getOperatorById( + uint64 operatorId + ) external view override returns (address, uint256, uint32, address, bool, bool) { return ssvNetwork.getOperatorById(operatorId); } @@ -115,20 +117,15 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getNetworkEarnings(); } - function getOperatorFeeIncreaseLimit() external view override returns (uint64 operatorMaxFeeIncrease) { + function getOperatorFeeIncreaseLimit() external view override returns (uint64) { return ssvNetwork.getOperatorFeeIncreaseLimit(); } - function getMaximumOperatorFee() external view override returns (uint64 operatorMaxFee) { + function getMaximumOperatorFee() external view override returns (uint64) { return ssvNetwork.getMaximumOperatorFee(); } - function getOperatorFeePeriods() - external - view - override - returns (uint64 declareOperatorFeePeriod, uint64 executeOperatorFeePeriod) - { + function getOperatorFeePeriods() external view override returns (uint64, uint64) { return ssvNetwork.getOperatorFeePeriods(); } @@ -144,7 +141,11 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews return ssvNetwork.getValidatorsPerOperatorLimit(); } - function getVersion() external view override returns (string memory version) { + function getNetworkValidatorsCount() external view override returns (uint32) { + return ssvNetwork.getNetworkValidatorsCount(); + } + + function getVersion() external view override returns (string memory) { return ssvNetwork.getVersion(); } } diff --git a/contracts/audits/SSV Network-Final Audit Report.pdf b/contracts/audits/2023-03-42_Quantstamp_v1.0.0-rc3.pdf similarity index 100% rename from contracts/audits/SSV Network-Final Audit Report.pdf rename to contracts/audits/2023-03-42_Quantstamp_v1.0.0-rc3.pdf diff --git a/contracts/audits/2023-30-10_Quantstamp_v1.0.0.pdf b/contracts/audits/2023-30-10_Quantstamp_v1.0.0.pdf new file mode 100644 index 00000000..eb39d832 Binary files /dev/null and b/contracts/audits/2023-30-10_Quantstamp_v1.0.0.pdf differ diff --git a/contracts/libraries/RegisterAuth.sol b/contracts/deprecated/RegisterAuth.sol similarity index 70% rename from contracts/libraries/RegisterAuth.sol rename to contracts/deprecated/RegisterAuth.sol index 351721b3..48cc836d 100644 --- a/contracts/libraries/RegisterAuth.sol +++ b/contracts/deprecated/RegisterAuth.sol @@ -6,8 +6,10 @@ struct Authorization { bool registerValidator; } +/// @notice Deprecated. Notice that if the library is used again, +/// all the values in AuthData.authorization will still be available. library RegisterAuth { - uint256 constant private SSV_STORAGE_POSITION = uint256(keccak256("ssv.network.storage.auth")) - 1; + uint256 private constant SSV_STORAGE_POSITION = uint256(keccak256("ssv.network.storage.auth")) - 1; struct AuthData { mapping(address => Authorization) authorization; diff --git a/contracts/interfaces/ISSVClusters.sol b/contracts/interfaces/ISSVClusters.sol index 9910e9fd..07546dcf 100644 --- a/contracts/interfaces/ISSVClusters.sol +++ b/contracts/interfaces/ISSVClusters.sol @@ -57,6 +57,11 @@ interface ISSVClusters is ISSVNetworkCore { /// @param cluster Cluster where the withdrawal will be made function withdraw(uint64[] memory operatorIds, uint256 tokenAmount, Cluster memory cluster) external; + /// @notice Fires the exit event for a validator + /// @param publicKey The public key of the validator to be exited + /// @param operatorIds Array of IDs of operators managing the validator + function exitValidator(bytes calldata publicKey, uint64[] calldata operatorIds) external; + /** * @dev Emitted when the validator has been added. * @param publicKey The public key of a validator. @@ -74,11 +79,45 @@ interface ISSVClusters is ISSVNetworkCore { */ event ValidatorRemoved(address indexed owner, uint64[] operatorIds, bytes publicKey, Cluster cluster); + /** + * @dev Emitted when a cluster is liquidated. + * @param owner The owner of the liquidated cluster. + * @param operatorIds The operator IDs managing the cluster. + * @param cluster The liquidated cluster data. + */ event ClusterLiquidated(address indexed owner, uint64[] operatorIds, Cluster cluster); + /** + * @dev Emitted when a cluster is reactivated. + * @param owner The owner of the reactivated cluster. + * @param operatorIds The operator IDs managing the cluster. + * @param cluster The reactivated cluster data. + */ event ClusterReactivated(address indexed owner, uint64[] operatorIds, Cluster cluster); + /** + * @dev Emitted when tokens are withdrawn from a cluster. + * @param owner The owner of the cluster. + * @param operatorIds The operator IDs managing the cluster. + * @param value The amount of tokens withdrawn. + * @param cluster The cluster from which tokens were withdrawn. + */ event ClusterWithdrawn(address indexed owner, uint64[] operatorIds, uint256 value, Cluster cluster); + /** + * @dev Emitted when tokens are deposited into a cluster. + * @param owner The owner of the cluster. + * @param operatorIds The operator IDs managing the cluster. + * @param value The amount of SSV tokens deposited. + * @param cluster The cluster into which SSV tokens were deposited. + */ event ClusterDeposited(address indexed owner, uint64[] operatorIds, uint256 value, Cluster cluster); + + /** + * @dev Emitted when a validator begins the exit process. + * @param owner The owner of the exiting validator. + * @param operatorIds The operator IDs managing the validator. + * @param publicKey The public key of the exiting validator. + */ + event ValidatorExited(address owner, uint64[] operatorIds, bytes indexed publicKey); } diff --git a/contracts/interfaces/ISSVNetwork.sol b/contracts/interfaces/ISSVNetwork.sol index de6e5e73..e88a56a6 100644 --- a/contracts/interfaces/ISSVNetwork.sol +++ b/contracts/interfaces/ISSVNetwork.sol @@ -31,8 +31,4 @@ interface ISSVNetwork { function setFeeRecipientAddress(address feeRecipientAddress) external; function updateModule(SSVModules moduleId, address moduleAddress) external; - - function setRegisterAuth(address userAddress, bool authOperators, bool authValidators) external; - - function getRegisterAuth(address userAddress) external view returns (bool authOperators, bool authValidators); } diff --git a/contracts/interfaces/ISSVViews.sol b/contracts/interfaces/ISSVViews.sol index 8b5c1657..b635013c 100644 --- a/contracts/interfaces/ISSVViews.sol +++ b/contracts/interfaces/ISSVViews.sol @@ -8,7 +8,7 @@ interface ISSVViews is ISSVNetworkCore { /// @param owner The address of the validator's owner /// @param publicKey The public key of the validator /// @return active A boolean indicating if the validator is active. If it does not exist, returns false. - function getValidator(address owner, bytes calldata publicKey) external view returns (bool active); + function getValidator(address owner, bytes calldata publicKey) external view returns (bool); /// @notice Gets the operator fee /// @param operatorId The ID of the operator @@ -46,7 +46,7 @@ interface ISSVViews is ISSVNetworkCore { /// @return isLiquidatable A boolean indicating if the cluster can be liquidated function isLiquidatable( address owner, - uint64[] memory operatorIds, + uint64[] calldata operatorIds, Cluster memory cluster ) external view returns (bool isLiquidatable); @@ -94,20 +94,17 @@ interface ISSVViews is ISSVNetworkCore { function getNetworkEarnings() external view returns (uint256 networkEarnings); /// @notice Gets the operator fee increase limit - /// @return operatorMaxFeeIncrease The maximum limit of operator fee increase - function getOperatorFeeIncreaseLimit() external view returns (uint64 operatorMaxFeeIncrease); + /// @return The maximum limit of operator fee increase + function getOperatorFeeIncreaseLimit() external view returns (uint64); /// @notice Gets the operator maximum fee for operators that use SSV token - /// @return operatorMaxFee The maximum fee value (SSV) - function getMaximumOperatorFee() external view returns (uint64 operatorMaxFee); + /// @return The maximum fee value (SSV) + function getMaximumOperatorFee() external view returns (uint64); /// @notice Gets the periods of operator fee declaration and execution - /// @return declareOperatorFeePeriod The period for declaring operator fee - /// @return executeOperatorFeePeriod The period for executing operator fee - function getOperatorFeePeriods() - external - view - returns (uint64 declareOperatorFeePeriod, uint64 executeOperatorFeePeriod); + /// @return The period for declaring operator fee + /// @return The period for executing operator fee + function getOperatorFeePeriods() external view returns (uint64, uint64); /// @notice Gets the liquidation threshold period /// @return blocks The number of blocks for the liquidation threshold period @@ -121,7 +118,11 @@ interface ISSVViews is ISSVNetworkCore { /// @return validators The maximum number of validators per operator function getValidatorsPerOperatorLimit() external view returns (uint32 validators); + /// @notice Gets the total number of validators in the network + /// @return validatorsCount The total number of validators in the network + function getNetworkValidatorsCount() external view returns (uint32 validatorsCount); + /// @notice Gets the version of the contract - /// @return version The version of the contract - function getVersion() external view returns (string memory version); + /// @return The version of the contract + function getVersion() external view returns (string memory); } diff --git a/contracts/libraries/CoreLib.sol b/contracts/libraries/CoreLib.sol index e8d31429..81a49bd4 100644 --- a/contracts/libraries/CoreLib.sol +++ b/contracts/libraries/CoreLib.sol @@ -7,7 +7,7 @@ library CoreLib { event ModuleUpgraded(SSVModules indexed moduleId, address moduleAddress); function getVersion() internal pure returns (string memory) { - return "v1.0.0.rc3"; + return "v1.0.0.rc5"; } function transferBalance(address to, uint256 amount) internal { diff --git a/contracts/libraries/OperatorLib.sol b/contracts/libraries/OperatorLib.sol index ef3a6e01..f0672d59 100644 --- a/contracts/libraries/OperatorLib.sol +++ b/contracts/libraries/OperatorLib.sol @@ -34,18 +34,19 @@ library OperatorLib { uint64[] memory operatorIds, bool increaseValidatorCount, uint32 deltaValidatorCount, - StorageData storage s + StorageData storage s, + StorageProtocol storage sp ) internal returns (uint64 clusterIndex, uint64 burnRate) { - for (uint256 i; i < operatorIds.length; ) { + uint256 operatorsLength = operatorIds.length; + + for (uint256 i; i < operatorsLength; ) { uint64 operatorId = operatorIds[i]; ISSVNetworkCore.Operator storage operator = s.operators[operatorId]; if (operator.snapshot.block != 0) { updateSnapshotSt(operator); if (!increaseValidatorCount) { operator.validatorCount -= deltaValidatorCount; - } else if ( - (operator.validatorCount += deltaValidatorCount) > SSVStorageProtocol.load().validatorsPerOperatorLimit - ) { + } else if ((operator.validatorCount += deltaValidatorCount) > sp.validatorsPerOperatorLimit) { revert ISSVNetworkCore.ExceedValidatorLimit(); } burnRate += operator.fee; diff --git a/contracts/libraries/Types.sol b/contracts/libraries/Types.sol index f0e3257e..e0f49156 100644 --- a/contracts/libraries/Types.sol +++ b/contracts/libraries/Types.sol @@ -11,7 +11,7 @@ library Types64 { library Types256 { function shrink(uint256 value) internal pure returns (uint64) { - require(value <= (2 ** 64 * DEDUCTED_DIGITS), "Max value exceeded"); + require(value < (2 ** 64 * DEDUCTED_DIGITS), "Max value exceeded"); return uint64(shrinkable(value) / DEDUCTED_DIGITS); } diff --git a/contracts/libraries/ValidatorLib.sol b/contracts/libraries/ValidatorLib.sol new file mode 100644 index 00000000..ad98ee5e --- /dev/null +++ b/contracts/libraries/ValidatorLib.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.18; + +import "../interfaces/ISSVNetworkCore.sol"; +import "./SSVStorage.sol"; + +library ValidatorLib { + function validateState( + bytes calldata publicKey, + uint64[] calldata operatorIds, + StorageData storage s + ) internal view returns (bytes32 hashedValidator) { + hashedValidator = keccak256(abi.encodePacked(publicKey, msg.sender)); + bytes32 validatorData = s.validatorPKs[hashedValidator]; + + if (validatorData == bytes32(0)) { + revert ISSVNetworkCore.ValidatorDoesNotExist(); + } + bytes32 mask = ~bytes32(uint256(1)); // All bits set to 1 except LSB + + bytes32 hashedOperatorIds = keccak256(abi.encodePacked(operatorIds)) & mask; // Clear LSB of provided operator ids + if ((validatorData & mask) != hashedOperatorIds) { + // Clear LSB of stored validator data and compare + revert ISSVNetworkCore.IncorrectValidatorState(); + } + } +} diff --git a/contracts/modules/SSVClusters.sol b/contracts/modules/SSVClusters.sol index 8d1df9aa..b05355e2 100644 --- a/contracts/modules/SSVClusters.sol +++ b/contracts/modules/SSVClusters.sol @@ -6,6 +6,7 @@ import "../libraries/ClusterLib.sol"; import "../libraries/OperatorLib.sol"; import "../libraries/ProtocolLib.sol"; import "../libraries/CoreLib.sol"; +import "../libraries/ValidatorLib.sol"; import "../libraries/SSVStorage.sol"; import "../libraries/SSVStorageProtocol.sol"; @@ -145,27 +146,14 @@ contract SSVClusters is ISSVClusters { ) external override { StorageData storage s = SSVStorage.load(); - bytes32 hashedValidator = keccak256(abi.encodePacked(publicKey, msg.sender)); - - bytes32 mask = ~bytes32(uint256(1)); // All bits set to 1 except LSB - bytes32 validatorData = s.validatorPKs[hashedValidator]; - - if (validatorData == bytes32(0)) { - revert ValidatorDoesNotExist(); - } - - bytes32 hashedOperatorIds = keccak256(abi.encodePacked(operatorIds)) & mask; // Clear LSB of provided operator ids - if ((validatorData & mask) != hashedOperatorIds) { - // Clear LSB of stored validator data and compare - revert IncorrectValidatorState(); - } + bytes32 hashedValidator = ValidatorLib.validateState(publicKey, operatorIds, s); bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s); { if (cluster.active) { - (uint64 clusterIndex, ) = OperatorLib.updateOperators(operatorIds, false, 1, s); StorageProtocol storage sp = SSVStorageProtocol.load(); + (uint64 clusterIndex, ) = OperatorLib.updateOperators(operatorIds, false, 1, s, sp); cluster.updateClusterData(clusterIndex, sp.currentNetworkFeeIndex()); @@ -182,7 +170,7 @@ contract SSVClusters is ISSVClusters { emit ValidatorRemoved(msg.sender, operatorIds, publicKey, cluster); } - function liquidate(address clusterOwner, uint64[] memory 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); @@ -194,7 +182,8 @@ contract SSVClusters is ISSVClusters { operatorIds, false, cluster.validatorCount, - s + s, + sp ); cluster.updateBalance(clusterIndex, sp.currentNetworkFeeIndex()); @@ -244,7 +233,8 @@ contract SSVClusters is ISSVClusters { operatorIds, true, cluster.validatorCount, - s + s, + sp ); cluster.balance += amount; @@ -344,4 +334,10 @@ contract SSVClusters is ISSVClusters { emit ClusterWithdrawn(msg.sender, operatorIds, amount, cluster); } + + function exitValidator(bytes calldata publicKey, uint64[] calldata operatorIds) external override { + ValidatorLib.validateState(publicKey, operatorIds, SSVStorage.load()); + + emit ValidatorExited(msg.sender, operatorIds, publicKey); + } } diff --git a/contracts/modules/SSVDAO.sol b/contracts/modules/SSVDAO.sol index cec7a6cf..e3e0a700 100644 --- a/contracts/modules/SSVDAO.sol +++ b/contracts/modules/SSVDAO.sol @@ -34,6 +34,7 @@ contract SSVDAO is ISSVDAO { } sp.daoBalance = networkBalance - shrunkAmount; + sp.daoIndexBlockNumber = uint32(block.number); CoreLib.transferBalance(msg.sender, amount); diff --git a/contracts/modules/SSVOperators.sol b/contracts/modules/SSVOperators.sol index d6d33fc5..b27455f7 100644 --- a/contracts/modules/SSVOperators.sol +++ b/contracts/modules/SSVOperators.sol @@ -65,9 +65,7 @@ contract SSVOperators is ISSVOperators { 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()); @@ -169,8 +167,8 @@ contract SSVOperators is ISSVOperators { operator.fee = shrunkAmount; s.operators[operatorId] = operator; - if (s.operatorFeeChangeRequests[operatorId].approvalBeginTime != 0) - delete s.operatorFeeChangeRequests[operatorId]; + delete s.operatorFeeChangeRequests[operatorId]; + emit OperatorFeeExecuted(msg.sender, operatorId, block.number, fee); } diff --git a/contracts/modules/SSVViews.sol b/contracts/modules/SSVViews.sol index 2c6c2070..48a7fc17 100644 --- a/contracts/modules/SSVViews.sol +++ b/contracts/modules/SSVViews.sol @@ -21,21 +21,20 @@ contract SSVViews is ISSVViews { /* Validator External View Functions */ /*************************************/ - function getValidator(address clusterOwner, bytes calldata publicKey) external view override returns (bool active) { + function getValidator(address clusterOwner, bytes calldata publicKey) external view override returns (bool) { bytes32 validatorData = SSVStorage.load().validatorPKs[keccak256(abi.encodePacked(publicKey, clusterOwner))]; if (validatorData == bytes32(0)) return false; bytes32 activeFlag = validatorData & bytes32(uint256(1)); // Retrieve LSB of stored value return activeFlag == bytes32(uint256(1)); - } /************************************/ /* Operator External View Functions */ /************************************/ - function getOperatorFee(uint64 operatorId) external view override returns (uint256 fee) { + function getOperatorFee(uint64 operatorId) external view override returns (uint256) { return SSVStorage.load().operators[operatorId].fee.expand(); } @@ -172,20 +171,15 @@ contract SSVViews is ISSVViews { return SSVStorageProtocol.load().networkTotalEarnings().expand(); } - function getOperatorFeeIncreaseLimit() external view override returns (uint64 operatorMaxFeeIncrease) { + function getOperatorFeeIncreaseLimit() external view override returns (uint64) { return SSVStorageProtocol.load().operatorMaxFeeIncrease; } - function getMaximumOperatorFee() external view override returns (uint64 operatorMaxFee) { + function getMaximumOperatorFee() external view override returns (uint64) { return SSVStorageProtocol.load().operatorMaxFee; } - function getOperatorFeePeriods() - external - view - override - returns (uint64 declareOperatorFeePeriod, uint64 executeOperatorFeePeriod) - { + function getOperatorFeePeriods() external view override returns (uint64, uint64) { return (SSVStorageProtocol.load().declareOperatorFeePeriod, SSVStorageProtocol.load().executeOperatorFeePeriod); } @@ -201,7 +195,11 @@ contract SSVViews is ISSVViews { return SSVStorageProtocol.load().validatorsPerOperatorLimit; } - function getVersion() external pure override returns (string memory version) { + function getNetworkValidatorsCount() external view override returns (uint32) { + return SSVStorageProtocol.load().daoValidatorCount; + } + + function getVersion() external pure override returns (string memory) { return CoreLib.getVersion(); } } diff --git a/contracts/test/SSVNetworkUpgrade.sol b/contracts/test/SSVNetworkUpgrade.sol index c859582f..c7288c17 100644 --- a/contracts/test/SSVNetworkUpgrade.sol +++ b/contracts/test/SSVNetworkUpgrade.sol @@ -286,6 +286,13 @@ contract SSVNetworkUpgrade is ); } + function exitValidator(bytes calldata publicKey, uint64[] calldata operatorIds) external override { + _delegateCall( + SSVStorage.load().ssvContracts[SSVModules.SSV_CLUSTERS], + abi.encodeWithSignature("exitValidator(bytes,uint64[]))", publicKey, operatorIds) + ); + } + function updateNetworkFee(uint256 fee) external override onlyOwner { _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_DAO], @@ -336,7 +343,7 @@ contract SSVNetworkUpgrade is } function updateMaximumOperatorFee(uint64 maxFee) external override { - _delegateCall( + _delegateCall( SSVStorage.load().ssvContracts[SSVModules.SSV_DAO], abi.encodeWithSignature("updateMaximumOperatorFee(uint64)", maxFee) ); diff --git a/contracts/test/SSVViewsT.sol b/contracts/test/SSVViewsT.sol index 428f595f..b9d756ff 100644 --- a/contracts/test/SSVViewsT.sol +++ b/contracts/test/SSVViewsT.sol @@ -208,6 +208,10 @@ contract SSVViewsT is ISSVViews { return CoreLibT.getVersion(); } + function getNetworkValidatorsCount() external view override returns (uint32) { + return SSVStorageProtocol.load().daoValidatorCount; + } + function getMinOperatorsPerCluster() external view returns (uint64) { return SSVStorageUpgrade.load().minOperatorsPerCluster; } diff --git a/contracts/test/interfaces/ISSVNetworkT.sol b/contracts/test/interfaces/ISSVNetworkT.sol index eaa3d0e3..84786b1b 100644 --- a/contracts/test/interfaces/ISSVNetworkT.sol +++ b/contracts/test/interfaces/ISSVNetworkT.sol @@ -8,8 +8,6 @@ import "../../interfaces/ISSVDAO.sol"; import "../../interfaces/ISSVViews.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../../libraries/RegisterAuth.sol"; - interface ISSVNetworkT { function initialize( IERC20 token_, diff --git a/contracts/test/libraries/CoreLibT.sol b/contracts/test/libraries/CoreLibT.sol index 9125938f..9cf935be 100644 --- a/contracts/test/libraries/CoreLibT.sol +++ b/contracts/test/libraries/CoreLibT.sol @@ -6,7 +6,7 @@ import "../../libraries/SSVStorage.sol"; library CoreLibT { function getVersion() internal pure returns (string memory) { - return "v1.0.0.rc3"; + return "v1.0.0.rc5"; } function transfer(address to, uint256 amount) internal { diff --git a/contracts/token/SSVToken.sol b/contracts/token/SSVToken.sol new file mode 100644 index 00000000..df3588d8 --- /dev/null +++ b/contracts/token/SSVToken.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.4; + +import "@openzeppelin/contracts/utils/Context.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; + +/** + * @title SSV Token + */ +contract SSVToken is Ownable, ERC20, ERC20Burnable { + constructor() ERC20("SSV Token", "SSV") { + } + + /** + * @dev Mint tokens + * @param to The target address + * @param amount The amount of token to mint + */ + function mint(address to, uint256 amount) external onlyOwner { + _mint(to, amount); + } +} \ No newline at end of file diff --git a/docs/local-dev.md b/docs/local-dev.md index fcfc4fbc..d159e826 100644 --- a/docs/local-dev.md +++ b/docs/local-dev.md @@ -12,15 +12,16 @@ Execute the steps to set up all tools needed. Copy [.env.example](../.env.example) to `.env` and edit to suit. - `[NETWORK]_ETH_NODE_URL` RPC URL of the node - `[NETWORK]_OWNER_PRIVATE_KEY` Private key of the deployer account, without 0x prefix -- `GAS_PRICE` example 30000000000 -- `GAS` example 8000000 -- `ETHERSCAN_KEY` etherescan API key to verify deployed contracts -- `SSVTOKEN_ADDRESS` SSV Token contract address to be used in custom networks. Keep it empty to deploy a mocked SSV token. -- `MINIMUM_BLOCKS_BEFORE_LIQUIDATION` a number of blocks before the cluster enters into a liquidatable state. Example: 214800 = 30 days -- `OPERATOR_MAX_FEE_INCREASE` the fee increase limit in percentage with this format: 100% = 10000, 10% = 1000 - using 10000 to represent 2 digit precision -- `DECLARE_OPERATOR_FEE_PERIOD` the period in which an operator can declare a fee change (seconds) -- `EXECUTE_OPERATOR_FEE_PERIOD` the period in which an operator fee change can be executed (seconds) -- `VALIDATORS_PER_OPERATOR_LIMIT` the number of validators an operator can manage +- `GAS_PRICE` Example 30000000000 +- `GAS` Example 8000000 +- `ETHERSCAN_KEY` Etherescan API key to verify deployed contracts +- `SSV_TOKEN_ADDRESS` SSV Token contract address to be used in custom networks. Keep it empty to deploy a mocked SSV token. +- `MINIMUM_BLOCKS_BEFORE_LIQUIDATION` A number of blocks before the cluster enters into a liquidatable state. Example: 214800 = 30 days +- `OPERATOR_MAX_FEE_INCREASE` The fee increase limit in percentage with this format: 100% = 10000, 10% = 1000 - using 10000 to represent 2 digit precision +- `DECLARE_OPERATOR_FEE_PERIOD` The period in which an operator can declare a fee change (seconds) +- `EXECUTE_OPERATOR_FEE_PERIOD` The period in which an operator fee change can be executed (seconds) +- `VALIDATORS_PER_OPERATOR_LIMIT` The number of validators an operator can manage +- `MINIMUM_LIQUIDATION_COLLATERAL` The lowest number in wei a cluster can have before its liquidatable #### Network configuration diff --git a/docs/tasks.md b/docs/tasks.md index 2379a796..240817a4 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -80,7 +80,7 @@ POSITIONAL ARGUMENTS: params Function parameters Example: -npx hardhat --network goerli_testnet upgrade:proxy --proxyAddress 0x1234... --contract SSVNetworkV2 --initFunction initializev2 --params param1 param2 +npx hardhat --network goerli_testnet upgrade:proxy --proxy-address 0x1234... --contract SSVNetworkV2 --init-function initializev2 param1 param2 ``` ### Update a module @@ -100,7 +100,7 @@ OPTIONS: Example: Update 'SSVOperators' module contract in the SSVNetwork -npx hardhat --network goerli_testnet update:module --module SSVOperators --attach-module true --proxyAddress 0x1234... +npx hardhat --network goerli_testnet update:module --module SSVOperators --attach-module true --proxy-address 0x1234... ``` ### Upgrade a library diff --git a/hardhat.config.ts b/hardhat.config.ts index 28662755..7b2d5eb2 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -24,6 +24,9 @@ const config: HardhatUserConfig = { }, solidity: { compilers: [ + { + version: "0.8.4", + }, { version: '0.8.18', settings: { @@ -48,7 +51,17 @@ const config: HardhatUserConfig = { etherscan: { // Your API key for Etherscan // Obtain one at https://etherscan.io/ - apiKey: process.env.ETHERSCAN_KEY + apiKey: process.env.ETHERSCAN_KEY, + customChains: [ + { + network: "holesky", + chainId: 17000, + urls: { + apiURL: "https://api-holesky.etherscan.io/api", + browserURL: "https://holesky.etherscan.io" + } + } + ] }, gasReporter: { enabled: true, @@ -78,6 +91,27 @@ if (process.env.GOERLI_ETH_NODE_URL) { } } +if (process.env.HOLESKY_ETH_NODE_URL) { + const sharedConfig = { + url: process.env.HOLESKY_ETH_NODE_URL, + accounts: [`0x${process.env.HOLESKY_OWNER_PRIVATE_KEY}`], + gasPrice: +(process.env.GAS_PRICE || ''), + gas: +(process.env.GAS || ''), + }; + //@ts-ignore + config.networks = { + ...config.networks, + holesky_development: { + ...sharedConfig, + ssvToken: '0x68A8DDD7a59A900E0657e9f8bbE02B70c947f25F' + } as SSVNetworkConfig, + holesky_testnet: { + ...sharedConfig, + ssvToken: '0xad45A78180961079BFaeEe349704F411dfF947C6' + } as SSVNetworkConfig, + } +} + if (process.env.MAINNET_ETH_NODE_URL) { //@ts-ignore config.networks = { diff --git a/tasks/deploy.ts b/tasks/deploy.ts index 84f3a8ec..8a3ad9b0 100644 --- a/tasks/deploy.ts +++ b/tasks/deploy.ts @@ -88,6 +88,18 @@ subtask("deploy:module", "Deploys a new module contract") return moduleAddress; }); +task("deploy:token", "Deploys SSV Token") + .setAction(async ({ }, hre) => { + console.log('Deploying SSV Network Token'); + + const ssvTokenFactory = await ethers.getContractFactory('SSVToken'); + const ssvToken = await ssvTokenFactory.deploy(); + await ssvToken.deployed(); + + console.log(`SSV Network Token deployed to: ${ssvToken.address}`); + + }); + /** * @title Hardhat subtask to deploy or fetch an SSV Token contract. * The ssvToken parameter in the hardhat's network section, specifies the address of the SSV Token contract. diff --git a/test/account/deposit.ts b/test/account/deposit.ts index c724eb19..2ba6b7d3 100644 --- a/test/account/deposit.ts +++ b/test/account/deposit.ts @@ -15,7 +15,7 @@ describe('Deposit Tests', () => { ssvViews = metadata.ssvViews; // Register operators - await helpers.registerOperators(0, 12, helpers.CONFIG.minimalOperatorFee); + await helpers.registerOperators(0, 14, helpers.CONFIG.minimalOperatorFee); // Define the operator fee minDepositAmount = (helpers.CONFIG.minimalBlocksBeforeLiquidation + 10) * helpers.CONFIG.minimalOperatorFee * 4; diff --git a/test/account/withdraw.ts b/test/account/withdraw.ts index 6b4d5419..10ad1a7f 100644 --- a/test/account/withdraw.ts +++ b/test/account/withdraw.ts @@ -5,24 +5,25 @@ import { expect } from 'chai'; import { trackGas, GasGroup } from '../helpers/gas-usage'; // Declare globals -let ssvNetworkContract: any, ssvToken: any, cluster1: any, minDepositAmount: any; +let ssvNetworkContract: any, ssvViews: any, ssvToken: any, cluster1: any, minDepositAmount: any; describe('Withdraw Tests', () => { beforeEach(async () => { // Initialize contract const metadata = (await helpers.initializeContract()); ssvNetworkContract = metadata.contract; + ssvNetworkContract = metadata.contract; + ssvViews = metadata.ssvViews; ssvToken = metadata.ssvToken; // Register operators - await helpers.registerOperators(0, 12, helpers.CONFIG.minimalOperatorFee); + await helpers.registerOperators(0, 14, helpers.CONFIG.minimalOperatorFee); minDepositAmount = (helpers.CONFIG.minimalBlocksBeforeLiquidation + 10) * helpers.CONFIG.minimalOperatorFee * 4; // Register validators // cold register await helpers.coldRegisterValidator(); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[4].address, false, true); await ssvToken.connect(helpers.DB.owners[4]).approve(ssvNetworkContract.address, minDepositAmount); const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[4]).registerValidator( helpers.DataGenerator.publicKey(1), @@ -49,19 +50,19 @@ describe('Withdraw Tests', () => { }); it('Withdraw from operator balance emits "OperatorWithdrawn"', async () => { - await expect(ssvNetworkContract.connect(helpers.DB.owners[0])['withdrawOperatorEarnings(uint64,uint256)'](1, helpers.CONFIG.minimalOperatorFee)).to.emit(ssvNetworkContract, 'OperatorWithdrawn'); + await expect(ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawOperatorEarnings(1, helpers.CONFIG.minimalOperatorFee)).to.emit(ssvNetworkContract, 'OperatorWithdrawn'); }); it('Withdraw from operator balance gas limits', async () => { - await trackGas(ssvNetworkContract.connect(helpers.DB.owners[0])['withdrawOperatorEarnings(uint64,uint256)'](1, helpers.CONFIG.minimalOperatorFee), [GasGroup.WITHDRAW_OPERATOR_BALANCE]); + await trackGas(ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawOperatorEarnings(1, helpers.CONFIG.minimalOperatorFee), [GasGroup.WITHDRAW_OPERATOR_BALANCE]); }); it('Withdraw the total operator balance emits "OperatorWithdrawn"', async () => { - await expect(ssvNetworkContract.connect(helpers.DB.owners[0])['withdrawAllOperatorEarnings(uint64)'](1)).to.emit(ssvNetworkContract, 'OperatorWithdrawn'); + await expect(ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawAllOperatorEarnings(1)).to.emit(ssvNetworkContract, 'OperatorWithdrawn'); }); it('Withdraw the total operator balance gas limits', async () => { - await trackGas(ssvNetworkContract.connect(helpers.DB.owners[0])['withdrawAllOperatorEarnings(uint64)'](1), [GasGroup.WITHDRAW_OPERATOR_BALANCE]); + await trackGas(ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawAllOperatorEarnings(1), [GasGroup.WITHDRAW_OPERATOR_BALANCE]); }); it('Withdraw from a cluster that has a removed operator emits "ClusterWithdrawn"', async () => { @@ -73,6 +74,21 @@ describe('Withdraw Tests', () => { await expect(ssvNetworkContract.connect(helpers.DB.owners[4]).withdraw(cluster1.operatorIds, minDepositAmount, cluster1.cluster)).to.be.revertedWithCustomError(ssvNetworkContract, 'InsufficientBalance'); }); + it('Sequentially withdraw more than the cluster balance reverts "InsufficientBalance"', async () => { + const burnPerBlock = helpers.CONFIG.minimalOperatorFee * 4; + + cluster1 = await helpers.deposit(1, cluster1.owner, cluster1.operatorIds, (minDepositAmount * 2).toString(), cluster1.cluster); + expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.operatorIds, cluster1.cluster)).to.be.equals(minDepositAmount * 3 - (burnPerBlock * 2)); + + cluster1 = await helpers.withdraw(4, cluster1.operatorIds, minDepositAmount, cluster1.cluster); + expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.operatorIds, cluster1.cluster)).to.be.equals(minDepositAmount * 2 - (burnPerBlock * 3)); + + cluster1 = await helpers.withdraw(4, cluster1.operatorIds, minDepositAmount, cluster1.cluster); + expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.operatorIds, cluster1.cluster)).to.be.equals(minDepositAmount - (burnPerBlock * 4)); + + await expect(ssvNetworkContract.connect(helpers.DB.owners[4]).withdraw(cluster1.operatorIds, minDepositAmount, cluster1.cluster)).to.be.revertedWithCustomError(ssvNetworkContract, 'InsufficientBalance'); + }); + it('Withdraw from a liquidatable cluster reverts "InsufficientBalance" (liquidation threshold)', async () => { await utils.progressBlocks(20); await expect(ssvNetworkContract.connect(helpers.DB.owners[4]).withdraw(cluster1.operatorIds, 4000000000, cluster1.cluster)).to.be.revertedWithCustomError(ssvNetworkContract, 'InsufficientBalance'); @@ -89,20 +105,31 @@ describe('Withdraw Tests', () => { }); it('Withdraw balance from an operator I do not own reverts "CallerNotOwner"', async () => { - await expect(ssvNetworkContract.connect(helpers.DB.owners[2])['withdrawOperatorEarnings(uint64,uint256)'](1, minDepositAmount)).to.be.revertedWithCustomError(ssvNetworkContract, 'CallerNotOwner'); + await expect(ssvNetworkContract.connect(helpers.DB.owners[2]).withdrawOperatorEarnings(1, minDepositAmount)).to.be.revertedWithCustomError(ssvNetworkContract, 'CallerNotOwner'); }); it('Withdraw more than the operator balance reverts "InsufficientBalance"', async () => { - await expect(ssvNetworkContract.connect(helpers.DB.owners[0])['withdrawOperatorEarnings(uint64,uint256)'](1, minDepositAmount + await expect(ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawOperatorEarnings(1, minDepositAmount + )).to.be.revertedWithCustomError(ssvNetworkContract, 'InsufficientBalance'); + }); + + it('Sequentially withdraw more than the operator balance reverts "InsufficientBalance"', async () => { + await ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawOperatorEarnings(1, helpers.CONFIG.minimalOperatorFee * 3); + expect(await ssvViews.getOperatorEarnings(1)).to.be.equals(helpers.CONFIG.minimalOperatorFee * 4 - helpers.CONFIG.minimalOperatorFee * 3); + + await ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawOperatorEarnings(1, helpers.CONFIG.minimalOperatorFee * 3); + expect(await ssvViews.getOperatorEarnings(1)).to.be.equals(helpers.CONFIG.minimalOperatorFee * 6 - helpers.CONFIG.minimalOperatorFee * 6); + + await expect(ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawOperatorEarnings(1, helpers.CONFIG.minimalOperatorFee * 3 )).to.be.revertedWithCustomError(ssvNetworkContract, 'InsufficientBalance'); }); it('Withdraw the total balance from an operator I do not own reverts "CallerNotOwner"', async () => { - await expect(ssvNetworkContract.connect(helpers.DB.owners[2])['withdrawAllOperatorEarnings(uint64)'](12)).to.be.revertedWithCustomError(ssvNetworkContract, 'CallerNotOwner'); + await expect(ssvNetworkContract.connect(helpers.DB.owners[2]).withdrawAllOperatorEarnings(12)).to.be.revertedWithCustomError(ssvNetworkContract, 'CallerNotOwner'); }); it('Withdraw more than the operator total balance reverts "InsufficientBalance"', async () => { - await expect(ssvNetworkContract.connect(helpers.DB.owners[0])['withdrawAllOperatorEarnings(uint64)'](12)).to.be.revertedWithCustomError(ssvNetworkContract, 'InsufficientBalance'); + await expect(ssvNetworkContract.connect(helpers.DB.owners[0]).withdrawAllOperatorEarnings(14)).to.be.revertedWithCustomError(ssvNetworkContract, 'InsufficientBalance'); }); it('Withdraw from a cluster without validators', async () => { diff --git a/test/dao/network-fee-change.ts b/test/dao/network-fee-change.ts index ae67b6a0..88290138 100644 --- a/test/dao/network-fee-change.ts +++ b/test/dao/network-fee-change.ts @@ -22,6 +22,12 @@ describe('Network Fee Tests', () => { )).to.emit(ssvNetworkContract, 'NetworkFeeUpdated').withArgs(0, networkFee); }); + it('Change network fee providing UINT64 max value reverts "Max value exceeded"', async () => { + const amount = (ethers.BigNumber.from(2).pow(64)).mul(ethers.BigNumber.from(1e8)); + await expect(ssvNetworkContract.updateNetworkFee(amount + )).to.be.revertedWith('Max value exceeded'); + }); + it('Change network fee when it was set emits "NetworkFeeUpdated"', async () => { const initialNetworkFee = helpers.CONFIG.minimalOperatorFee; await ssvNetworkContract.updateNetworkFee(initialNetworkFee); diff --git a/test/dao/network-fee-withdraw.ts b/test/dao/network-fee-withdraw.ts index 7c40f9c6..8a56a351 100644 --- a/test/dao/network-fee-withdraw.ts +++ b/test/dao/network-fee-withdraw.ts @@ -18,7 +18,7 @@ describe('DAO Network Fee Withdraw Tests', () => { networkFee = helpers.CONFIG.minimalOperatorFee; // Register operators - await helpers.registerOperators(0, 12, helpers.CONFIG.minimalOperatorFee); + await helpers.registerOperators(0, 14, helpers.CONFIG.minimalOperatorFee); burnPerBlock = helpers.CONFIG.minimalOperatorFee * 4 + networkFee; minDepositAmount = helpers.CONFIG.minimalBlocksBeforeLiquidation * burnPerBlock; @@ -26,7 +26,6 @@ describe('DAO Network Fee Withdraw Tests', () => { // Set network fee await ssvNetworkContract.updateNetworkFee(networkFee); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[0].address, false, true); // Register validators // cold register await helpers.coldRegisterValidator(); @@ -34,9 +33,6 @@ describe('DAO Network Fee Withdraw Tests', () => { await helpers.registerValidators(4, 1, minDepositAmount, helpers.DataGenerator.cluster.new(), [GasGroup.REGISTER_VALIDATOR_NEW_STATE]); await utils.progressBlocks(10); - // Temporary till deposit logic not available - // Mint tokens - await helpers.DB.ssvToken.mint(ssvNetworkContract.address, minDepositAmount); }); it('Withdraw network earnings emits "NetworkEarningsWithdrawn"', async () => { @@ -69,4 +65,23 @@ describe('DAO Network Fee Withdraw Tests', () => { await expect(ssvNetworkContract.connect(helpers.DB.owners[3]).withdrawNetworkEarnings(amount )).to.be.revertedWith('Ownable: caller is not the owner'); }); + + it('Withdraw network earnings providing UINT64 max value reverts "Max value exceeded"', async () => { + const amount = (ethers.BigNumber.from(2).pow(64)).mul(ethers.BigNumber.from(1e8)); + await expect(ssvNetworkContract.withdrawNetworkEarnings(amount + )).to.be.revertedWith('Max value exceeded'); + }); + + it('Withdraw network earnings sequentially when not enough balance reverts "InsufficientBalance"', async () => { + const amount = await ssvViews.getNetworkEarnings() / 2; + + await ssvNetworkContract.withdrawNetworkEarnings(amount); + expect(await ssvViews.getNetworkEarnings()).to.be.equals(((networkFee * 13) + (networkFee * 11) - amount)); + + await ssvNetworkContract.withdrawNetworkEarnings(amount); + expect(await ssvViews.getNetworkEarnings()).to.be.equals(((networkFee * 14) + (networkFee * 12) - amount * 2)); + + await expect(ssvNetworkContract.withdrawNetworkEarnings(amount + )).to.be.revertedWithCustomError(ssvNetworkContract, 'InsufficientBalance'); + }); }); \ No newline at end of file diff --git a/test/dao/operational.ts b/test/dao/operational.ts index 88969e35..27ecdfc0 100644 --- a/test/dao/operational.ts +++ b/test/dao/operational.ts @@ -1,14 +1,19 @@ // Declare imports import * as helpers from '../helpers/contract-helpers'; +import { trackGas } from '../helpers/gas-usage'; +import { progressBlocks } from '../helpers/utils'; + import { expect } from 'chai'; -let ssvNetworkContract: any; +let ssvNetworkContract: any, ssvNetworkViews: any, firstCluster: any; // Declare globals describe('DAO operational Tests', () => { beforeEach(async () => { // Initialize contract - ssvNetworkContract = (await helpers.initializeContract()).contract; + const metadata = (await helpers.initializeContract()); + ssvNetworkContract = metadata.contract; + ssvNetworkViews = metadata.ssvViews; }); it('Starting the transfer process does not change owner', async () => { @@ -24,4 +29,56 @@ describe('DAO operational Tests', () => { expect(await ssvNetworkContract.owner()).equals(helpers.DB.owners[4].address); }); + it('Get the network validators count (add/remove validaotor)', async () => { + await helpers.registerOperators(0, 4, helpers.CONFIG.minimalOperatorFee); + + const deposit = (helpers.CONFIG.minimalBlocksBeforeLiquidation + 2) * helpers.CONFIG.minimalOperatorFee * 13; + + await helpers.DB.ssvToken.connect(helpers.DB.owners[4]).approve(ssvNetworkContract.address, deposit); + + const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[4]).registerValidator( + helpers.DataGenerator.publicKey(1), + [1, 2, 3, 4], + helpers.DataGenerator.shares(4), + deposit, + { + validatorCount: 0, + networkFeeIndex: 0, + index: 0, + balance: 0, + active: true + } + )); + + firstCluster = register.eventsByName.ValidatorAdded[0].args; + + expect((await ssvNetworkViews.getNetworkValidatorsCount())).to.equal(1); + + await ssvNetworkContract.connect(helpers.DB.owners[4]).removeValidator( + helpers.DataGenerator.publicKey(1), + firstCluster.operatorIds, + firstCluster.cluster + ); + expect((await ssvNetworkViews.getNetworkValidatorsCount())).to.equal(0); + }); + + it('Get the network validators count (add/remove validaotor)', async () => { + await helpers.registerOperators(0, 4, helpers.CONFIG.minimalOperatorFee); + + const deposit = (helpers.CONFIG.minimalBlocksBeforeLiquidation + 2) * helpers.CONFIG.minimalOperatorFee * 13; + firstCluster = await helpers.registerValidators(4, 1, deposit.toString(), helpers.DataGenerator.cluster.new()); + + expect((await ssvNetworkViews.getNetworkValidatorsCount())).to.equal(1); + + await progressBlocks(helpers.CONFIG.minimalBlocksBeforeLiquidation); + + await ssvNetworkContract.connect(helpers.DB.owners[4]).liquidate( + firstCluster.args.owner, + firstCluster.args.operatorIds, + firstCluster.args.cluster + ); + + expect((await ssvNetworkViews.getNetworkValidatorsCount())).to.equal(0); + }); + }); \ No newline at end of file diff --git a/test/deployment/deploy.ts b/test/deployment/deploy.ts index fd03fa5f..e4392f80 100644 --- a/test/deployment/deploy.ts +++ b/test/deployment/deploy.ts @@ -14,9 +14,18 @@ describe('Deployment tests', () => { ssvToken = metadata.ssvToken; }); - it('Upgrade SSVNetwork contract. Check new function execution', async () => { - await ssvNetworkContract.setRegisterAuth(DB.owners[1].address, true, false); + it('Check default values after deploying', async () => { + expect((await ssvNetworkViews.getNetworkValidatorsCount())).to.equal(0); + expect((await ssvNetworkViews.getNetworkEarnings())).to.equal(0); + expect((await ssvNetworkViews.getOperatorFeeIncreaseLimit())).to.equal(CONFIG.operatorMaxFeeIncrease); + expect((await ssvNetworkViews.getOperatorFeePeriods())).to.deep.equal([CONFIG.declareOperatorFeePeriod, CONFIG.executeOperatorFeePeriod]); + expect((await ssvNetworkViews.getLiquidationThresholdPeriod())).to.equal(CONFIG.minimalBlocksBeforeLiquidation); + expect((await ssvNetworkViews.getMinimumLiquidationCollateral())).to.equal(CONFIG.minimumLiquidationCollateral); + expect((await ssvNetworkViews.getValidatorsPerOperatorLimit())).to.equal(CONFIG.validatorsPerOperatorLimit); + expect((await ssvNetworkViews.getOperatorFeeIncreaseLimit())).to.equal(CONFIG.operatorMaxFeeIncrease); + }); + it('Upgrade SSVNetwork contract. Check new function execution', async () => { await ssvNetworkContract.connect(DB.owners[1]).registerOperator( DataGenerator.publicKey(0), CONFIG.minimalOperatorFee); @@ -96,41 +105,6 @@ describe('Deployment tests', () => { expect(await ssvNetworkViews.getNetworkFee()).to.be.equals(0); }); - it('Remove registerAuth from SSVNetwork contract', async () => { - const publicKey = DataGenerator.publicKey(4); - await ssvNetworkContract.setRegisterAuth(DB.owners[1].address, true, false); - - await ssvNetworkContract.connect(DB.owners[1]).registerOperator( - publicKey, - CONFIG.minimalOperatorFee); - - const SSVNetworkUpgrade = await ethers.getContractFactory("SSVNetworkUpgrade"); - const ssvNetworkUpgrade = await upgrades.upgradeProxy(ssvNetworkContract.address, SSVNetworkUpgrade, { - kind: 'uups', - unsafeAllow: ['delegatecall'] - }); - await ssvNetworkUpgrade.deployed(); - - expect(await ssvNetworkViews.getOperatorById(1)).to.deep.equal( - [DB.owners[1].address, // owner - CONFIG.minimalOperatorFee, // fee - 0, // validatorCount - ethers.constants.AddressZero, // whitelisted - false, // isPrivate - true // active - ]); - - await expect(ssvNetworkContract.connect(DB.owners[4]).registerOperator( - publicKey, - CONFIG.minimalOperatorFee - )).to.be.revertedWithCustomError(ssvNetworkContract, 'OperatorAlreadyExists'); - - await expect(ssvNetworkContract.connect(DB.owners[1]).registerOperator( - DataGenerator.publicKey(2), - CONFIG.minimalOperatorFee - )).to.emit(ssvNetworkContract, 'OperatorAdded').withArgs(2, DB.owners[1].address, DataGenerator.publicKey(2), CONFIG.minimalOperatorFee); - }); - it('Update a module (SSVOperators)', async () => { const ssvNetworkFactory = await ethers.getContractFactory('SSVNetwork'); const ssvNetwork = await ssvNetworkFactory.attach(ssvNetworkContract.address); diff --git a/test/deployment/version.ts b/test/deployment/version.ts index d13aa736..ac0866b5 100644 --- a/test/deployment/version.ts +++ b/test/deployment/version.ts @@ -20,7 +20,7 @@ describe('Version upgrade tests', () => { await ssvNetworkContract.updateModule(helpers.SSV_MODULES.SSV_VIEWS, viewsContract.address) - expect(await ssvNetworkViews.getVersion()).to.equal("v1.0.0.rc3"); + expect(await ssvNetworkViews.getVersion()).to.equal("v1.0.0.rc5"); }); }); \ No newline at end of file diff --git a/test/helpers/contract-helpers.ts b/test/helpers/contract-helpers.ts index 8bef455c..6a2b6f53 100644 --- a/test/helpers/contract-helpers.ts +++ b/test/helpers/contract-helpers.ts @@ -65,7 +65,7 @@ export const DataGenerator = { export const initializeContract = async () => { CONFIG = { - initialVersion: "v1.0.0.rc3", + initialVersion: "v1.0.0.rc5", operatorMaxFeeIncrease: 1000, declareOperatorFeePeriod: 3600, // HOUR executeOperatorFeePeriod: 86400, // DAY @@ -145,8 +145,6 @@ export const initializeContract = async () => { await DB.ssvNetwork.contract.deployed(); - await DB.ssvNetwork.contract.setRegisterAuth(DB.owners[0].address, true, true); - DB.ssvViews.contract = await upgrades.deployProxy(ssvViews, [ DB.ssvNetwork.contract.address ], @@ -170,7 +168,6 @@ export const initializeContract = async () => { }; export const registerOperators = async (ownerId: number, numberOfOperators: number, fee: string, gasGroups: GasGroup[] = [GasGroup.REGISTER_OPERATOR]) => { - await DB.ssvNetwork.contract.setRegisterAuth(DB.owners[ownerId].address, true, false); for (let i = 0; i < numberOfOperators; ++i) { const { eventsByName } = await trackGas( DB.ssvNetwork.contract.connect(DB.owners[ownerId]).registerOperator(DataGenerator.publicKey(i), fee), @@ -230,7 +227,6 @@ export const reactivate = async (ownerId: number, operatorIds: number[], amount: }; export const registerValidators = async (ownerId: number, numberOfValidators: number, amount: string, operatorIds: number[], gasGroups?: GasGroup[]) => { - await DB.ssvNetwork.contract.setRegisterAuth(DB.owners[ownerId].address, false, true); const validators: any = []; let args: any; // Register validators to contract @@ -260,8 +256,6 @@ export const registerValidators = async (ownerId: number, numberOfValidators: nu }; export const registerValidatorsRaw = async (ownerId: number, numberOfValidators: number, amount: string, operatorIds: number[], gasGroups?: GasGroup[]) => { - await DB.ssvNetwork.contract.setRegisterAuth(DB.owners[ownerId].address, false, true); - let cluster: any = { validatorCount: 0, networkFeeIndex: 0, @@ -294,13 +288,11 @@ export const getCluster = (payload: any) => ethers.utils.AbiCoder.prototype.enco ); export const coldRegisterValidator = async () => { - await DB.ssvNetwork.contract.setRegisterAuth(DB.owners[0].address, false, true); - await DB.ssvToken.approve(DB.ssvNetwork.contract.address, '1000000000000000'); await DB.ssvNetwork.contract.registerValidator( DataGenerator.publicKey(90), - [1, 2, 3, 4], - DataGenerator.shares(4), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], + DataGenerator.shares(14), '1000000000000000', { validatorCount: 0, diff --git a/test/helpers/gas-usage.ts b/test/helpers/gas-usage.ts index d67cd633..536812e7 100644 --- a/test/helpers/gas-usage.ts +++ b/test/helpers/gas-usage.ts @@ -28,9 +28,13 @@ export enum GasGroup { REGISTER_VALIDATOR_WITHOUT_DEPOSIT_13, REMOVE_VALIDATOR, + REMOVE_VALIDATOR_7, + REMOVE_VALIDATOR_10, + REMOVE_VALIDATOR_13, DEPOSIT, WITHDRAW_CLUSTER_BALANCE, WITHDRAW_OPERATOR_BALANCE, + VALIDATOR_EXIT, LIQUIDATE_CLUSTER_4, LIQUIDATE_CLUSTER_7, @@ -78,9 +82,14 @@ const MAX_GAS_PER_GROUP: any = { [GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT_13]: 394000, [GasGroup.REMOVE_VALIDATOR]: 113500, + [GasGroup.REMOVE_VALIDATOR_7]: 155000, + [GasGroup.REMOVE_VALIDATOR_10]: 196500, + [GasGroup.REMOVE_VALIDATOR_13]: 238000, [GasGroup.DEPOSIT]: 77500, [GasGroup.WITHDRAW_CLUSTER_BALANCE]: 94500, [GasGroup.WITHDRAW_OPERATOR_BALANCE]: 64900, + [GasGroup.VALIDATOR_EXIT]: 41500, + [GasGroup.LIQUIDATE_CLUSTER_4]: 129300, [GasGroup.LIQUIDATE_CLUSTER_7]: 170500, [GasGroup.LIQUIDATE_CLUSTER_10]: 211600, diff --git a/test/liquidate/liquidate.ts b/test/liquidate/liquidate.ts index e4346fc4..5691499f 100644 --- a/test/liquidate/liquidate.ts +++ b/test/liquidate/liquidate.ts @@ -22,7 +22,6 @@ describe('Liquidate Tests', () => { // cold register await helpers.coldRegisterValidator(); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, false, true); // first validator await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount); const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( diff --git a/test/liquidate/liquidated-cluster.ts b/test/liquidate/liquidated-cluster.ts index 4c33e168..a0afb027 100644 --- a/test/liquidate/liquidated-cluster.ts +++ b/test/liquidate/liquidated-cluster.ts @@ -15,7 +15,7 @@ describe('Liquidate Tests', () => { ssvViews = metadata.ssvViews; // Register operators - await helpers.registerOperators(0, 12, helpers.CONFIG.minimalOperatorFee); + await helpers.registerOperators(0, 14, helpers.CONFIG.minimalOperatorFee); networkFee = helpers.CONFIG.minimalOperatorFee; burnPerBlock = helpers.CONFIG.minimalOperatorFee * 4 + networkFee; @@ -23,7 +23,6 @@ describe('Liquidate Tests', () => { await ssvNetworkContract.updateNetworkFee(networkFee); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, false, true); // first validator await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount * 2); const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( @@ -125,8 +124,6 @@ describe('Liquidate Tests', () => { }); it('Remove validator -> withdraw -> try liquidate reverts "ClusterNotLiquidatable"', async () => { - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[2].address, false, true); - await helpers.DB.ssvToken.connect(helpers.DB.owners[2]).approve(ssvNetworkContract.address, minDepositAmount); const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[2]).registerValidator( helpers.DataGenerator.publicKey(2), diff --git a/test/liquidate/reactivate.ts b/test/liquidate/reactivate.ts index f19537d7..8a6c5d42 100644 --- a/test/liquidate/reactivate.ts +++ b/test/liquidate/reactivate.ts @@ -14,7 +14,7 @@ describe('Reactivate Tests', () => { ssvNetworkContract = metadata.contract; // Register operators - await helpers.registerOperators(0, 12, helpers.CONFIG.minimalOperatorFee); + await helpers.registerOperators(0, 14, helpers.CONFIG.minimalOperatorFee); minDepositAmount = (helpers.CONFIG.minimalBlocksBeforeLiquidation + 10) * helpers.CONFIG.minimalOperatorFee * 4; @@ -22,7 +22,6 @@ describe('Reactivate Tests', () => { // cold register await helpers.coldRegisterValidator(); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, false, true); // first validator await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount); const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( diff --git a/test/operators/others.ts b/test/operators/others.ts index 5de72c00..e40916da 100644 --- a/test/operators/others.ts +++ b/test/operators/others.ts @@ -14,7 +14,6 @@ describe('Others Operator Tests', () => { }); it('Add fee recipient address emits "FeeRecipientAddressUpdated"', async () => { - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, true, false); await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).setFeeRecipientAddress( helpers.DB.owners[2].address )) @@ -37,7 +36,6 @@ describe('Others Operator Tests', () => { }); it('Non-owner remove operator whitelisted address reverts "CallerNotOwner"', async () => { - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, true, false); const result = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerOperator( helpers.DataGenerator.publicKey(1), helpers.CONFIG.minimalOperatorFee @@ -63,7 +61,6 @@ describe('Others Operator Tests', () => { }); it('Non-owner update operator whitelisted address reverts "CallerNotOwner"', async () => { - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, true, false); const result = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerOperator( helpers.DataGenerator.publicKey(1), helpers.CONFIG.minimalOperatorFee diff --git a/test/operators/register-auth.ts b/test/operators/register-auth.ts deleted file mode 100644 index fb2abd48..00000000 --- a/test/operators/register-auth.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Declare imports -import * as helpers from '../helpers/contract-helpers'; -import { expect } from 'chai'; - -// Declare globals -let ssvNetworkContract: any; - -describe('Register Auth Operator Tests', () => { - before(async () => { - const metadata = (await helpers.initializeContract()); - ssvNetworkContract = metadata.contract; - }); - - it('Register auth and get auth data', async () => { - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[10].address, true, false); - expect(await ssvNetworkContract.getRegisterAuth(helpers.DB.owners[10].address)).to.deep.equal( - [true, false] - ); - - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[11].address, false, true); - expect(await ssvNetworkContract.getRegisterAuth(helpers.DB.owners[11].address)).to.deep.equal( - [false, true] - ) - - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[12].address, true, true); - expect(await ssvNetworkContract.getRegisterAuth(helpers.DB.owners[12].address)).to.deep.equal( - [true, true] - ) - - expect(await ssvNetworkContract.getRegisterAuth(helpers.DB.owners[5].address)).to.deep.equal( - [false, false] - ) - }); - - it('Register operator with unauthorized address reverts "NotAuthorized"', async () => { - await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).registerOperator( - helpers.DataGenerator.publicKey(12), - helpers.CONFIG.minimalOperatorFee - )).to.be.revertedWithCustomError(ssvNetworkContract, 'NotAuthorized'); - - }); - - it('Register operator with unauthorized address reverts "NotAuthorized" (2)', async () => { - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, false, true); - - await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).registerOperator( - helpers.DataGenerator.publicKey(12), - helpers.CONFIG.minimalOperatorFee - )).to.be.revertedWithCustomError(ssvNetworkContract, 'NotAuthorized'); - }); - - it('Register validator with unauthorized address reverts "NotAuthorized"', async () => { - await expect(ssvNetworkContract.connect(helpers.DB.owners[3]).registerValidator( - helpers.DataGenerator.publicKey(12), - [1, 2, 3, 4], - helpers.DataGenerator.shares(4), - 10000000, - { - validatorCount: 0, - networkFeeIndex: 0, - index: 0, - balance: 0, - active: true - } - )).to.be.revertedWithCustomError(ssvNetworkContract, 'NotAuthorized'); - - }); - - it('Register validator with unauthorized address reverts "NotAuthorized" (2)', async () => { - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[3].address, true, false); - - await expect(ssvNetworkContract.connect(helpers.DB.owners[3]).registerValidator( - helpers.DataGenerator.publicKey(12), - [1, 2, 3, 4], - helpers.DataGenerator.shares(4), - 10000000, - { - validatorCount: 0, - networkFeeIndex: 0, - index: 0, - balance: 0, - active: true - } - )).to.be.revertedWithCustomError(ssvNetworkContract, 'NotAuthorized'); - }); - -}); \ No newline at end of file diff --git a/test/operators/register.ts b/test/operators/register.ts index 480dea69..1e603f9c 100644 --- a/test/operators/register.ts +++ b/test/operators/register.ts @@ -11,8 +11,6 @@ describe('Register Operator Tests', () => { const metadata = (await helpers.initializeContract()); ssvNetworkContract = metadata.contract; ssvViews = metadata.ssvViews; - - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, true, false); }); it('Register operator emits "OperatorAdded"', async () => { diff --git a/test/operators/remove.ts b/test/operators/remove.ts index 56367e2d..c3bffdec 100644 --- a/test/operators/remove.ts +++ b/test/operators/remove.ts @@ -13,7 +13,7 @@ describe('Remove Operator Tests', () => { ssvViews = metadata.ssvViews; // Register operators - await helpers.registerOperators(0, 5, helpers.CONFIG.minimalOperatorFee); + await helpers.registerOperators(0, 14, helpers.CONFIG.minimalOperatorFee); // Register a validator // cold register @@ -26,9 +26,8 @@ describe('Remove Operator Tests', () => { }); it('Remove private operator emits "OperatorRemoved"', async () => { - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[0].address, true, false); const result = await trackGas(ssvNetworkContract.registerOperator( - helpers.DataGenerator.publicKey(12), + helpers.DataGenerator.publicKey(22), helpers.CONFIG.minimalOperatorFee )); const { operatorId } = result.eventsByName.OperatorAdded[0].args; @@ -53,7 +52,7 @@ describe('Remove Operator Tests', () => { }); it('Remove operator with 0 balance emits "OperatorWithdrawn"', async () => { - await expect(ssvNetworkContract.removeOperator(5)).not.to.emit(ssvNetworkContract, 'OperatorWithdrawn'); + await expect(ssvNetworkContract.removeOperator(14)).not.to.emit(ssvNetworkContract, 'OperatorWithdrawn'); }); it('Remove operator with a balance emits "OperatorWithdrawn"', async () => { diff --git a/test/operators/update-fee.ts b/test/operators/update-fee.ts index ed43f2ee..8d31054b 100644 --- a/test/operators/update-fee.ts +++ b/test/operators/update-fee.ts @@ -123,7 +123,6 @@ describe('Operator Fee Tests', () => { it('Declare fee too high reverts "FeeTooHigh" -> DAO updates limit -> declare fee emits "OperatorFeeDeclared"', async () => { const maxOperatorFee = 8e14; await ssvNetworkContract.updateMaximumOperatorFee(maxOperatorFee); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[3].address, true, false); await ssvNetworkContract.connect(helpers.DB.owners[3]).registerOperator(helpers.DataGenerator.publicKey(10), maxOperatorFee); const newOperatorFee = maxOperatorFee + maxOperatorFee / 10; diff --git a/test/sanity/balances.ts b/test/sanity/balances.ts index dfc9b883..876cecf3 100644 --- a/test/sanity/balances.ts +++ b/test/sanity/balances.ts @@ -15,7 +15,7 @@ describe('Balance Tests', () => { ssvViews = metadata.ssvViews; // Register operators - await helpers.registerOperators(0, 12, helpers.CONFIG.minimalOperatorFee); + await helpers.registerOperators(0, 14, helpers.CONFIG.minimalOperatorFee); networkFee = helpers.CONFIG.minimalOperatorFee; burnPerBlock = helpers.CONFIG.minimalOperatorFee * 4 + networkFee; @@ -79,20 +79,20 @@ describe('Balance Tests', () => { it('Check operators earnings in three blocks, one after the other', async () => { await utils.progressBlocks(1); - expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 2 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(2)).to.equal(helpers.CONFIG.minimalOperatorFee * 2 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 2 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(4)).to.equal(helpers.CONFIG.minimalOperatorFee * 2 + helpers.CONFIG.minimalOperatorFee * 3); + expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 2 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(2)).to.equal(helpers.CONFIG.minimalOperatorFee * 2 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 2 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(4)).to.equal(helpers.CONFIG.minimalOperatorFee * 2 + helpers.CONFIG.minimalOperatorFee * 2); await utils.progressBlocks(1); - expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(2)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(4)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee * 3); + expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(2)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(4)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee * 2); await utils.progressBlocks(1); - expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(2)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 3); - expect(await ssvViews.getOperatorEarnings(4)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 3); + expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(2)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(4)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 2); }); it('Check cluster balance with removed operator', async () => { @@ -128,10 +128,11 @@ describe('Balance Tests', () => { // update network fee // register a new validator with some shared operators // update network fee + // progress blocks in the process await utils.progressBlocks(1); - expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee); + expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 3 + helpers.CONFIG.minimalOperatorFee); expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.args.operatorIds, cluster1.args.cluster)).to.equal(minDepositAmount - burnPerBlock); expect(await ssvViews.getNetworkEarnings() - initNetworkFeeBalance).to.equal(networkFee * 2); @@ -148,28 +149,28 @@ describe('Balance Tests', () => { const cluster2 = await helpers.registerValidators(4, 1, minDep2.toString(), [3, 4, 5, 6]); await utils.progressBlocks(2); - expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 10 + helpers.CONFIG.minimalOperatorFee * 9); - expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 10 + helpers.CONFIG.minimalOperatorFee * 9 + helpers.CONFIG.minimalOperatorFee * 2); + expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 8 + helpers.CONFIG.minimalOperatorFee * 8); + expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 8 + helpers.CONFIG.minimalOperatorFee * 8 + helpers.CONFIG.minimalOperatorFee * 2); - expect(await ssvViews.getOperatorEarnings(5)).to.equal(helpers.CONFIG.minimalOperatorFee * 2); - expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.args.operatorIds, cluster1.args.cluster)).to.equal(minDepositAmount - burnPerBlock * 2 - newBurnPerBlock * 6); + expect(await ssvViews.getOperatorEarnings(5)).to.equal(helpers.CONFIG.minimalOperatorFee * 6 + helpers.CONFIG.minimalOperatorFee * 5); + expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.args.operatorIds, cluster1.args.cluster)).to.equal(minDepositAmount - burnPerBlock * 2 - newBurnPerBlock * 5); expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster2.args.operatorIds, cluster2.args.cluster)).to.equal(minDep2 - newBurnPerBlock * 2); // cold cluster + cluster1 * networkFee (4) + (cold cluster + cluster1 * newNetworkFee (5 + 5)) + cluster2 * newNetworkFee (2) - expect(await ssvViews.getNetworkEarnings() - initNetworkFeeBalance).to.equal(networkFee * 4 + newNetworkFee * 5 + newNetworkFee * 5 + newNetworkFee * 4); + expect(await ssvViews.getNetworkEarnings() - initNetworkFeeBalance).to.equal(networkFee * 4 + newNetworkFee * 5 + newNetworkFee * 4 + newNetworkFee * 3); await ssvNetworkContract.updateNetworkFee(networkFee); await utils.progressBlocks(4); - expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.args.operatorIds, cluster1.args.cluster)).to.equal(minDepositAmount - burnPerBlock * 2 - newBurnPerBlock * 7 - burnPerBlock * 4); + expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.args.operatorIds, cluster1.args.cluster)).to.equal(minDepositAmount - burnPerBlock * 2 - newBurnPerBlock * 6 - burnPerBlock * 4); expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster2.args.operatorIds, cluster2.args.cluster)).to.equal(minDep2 - newBurnPerBlock * 3 - burnPerBlock * 4); - expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 15 + helpers.CONFIG.minimalOperatorFee * 14); - expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 15 + helpers.CONFIG.minimalOperatorFee * 14 + helpers.CONFIG.minimalOperatorFee * 7); - expect(await ssvViews.getOperatorEarnings(5)).to.equal(helpers.CONFIG.minimalOperatorFee * 7); + expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 14 + helpers.CONFIG.minimalOperatorFee * 12); + expect(await ssvViews.getOperatorEarnings(3)).to.equal(helpers.CONFIG.minimalOperatorFee * 14 + helpers.CONFIG.minimalOperatorFee * 12 + helpers.CONFIG.minimalOperatorFee * 7); + expect(await ssvViews.getOperatorEarnings(5)).to.equal(helpers.CONFIG.minimalOperatorFee * 11 + helpers.CONFIG.minimalOperatorFee * 10); // cold cluster + cluster1 * networkFee (4) + (cold cluster + cluster1 * newNetworkFee (6 + 6)) + cluster2 * newNetworkFee (3) + (cold cluster + cluster1 + cluster2 * networkFee (4 + 4 + 4)) - expect(await ssvViews.getNetworkEarnings() - initNetworkFeeBalance).to.equal(networkFee * 4 + newNetworkFee * 7 + newNetworkFee * 7 + newNetworkFee * 3 + networkFee * 12); + expect(await ssvViews.getNetworkEarnings() - initNetworkFeeBalance).to.equal(networkFee * 4 + newNetworkFee * 6 + newNetworkFee * 6 + newNetworkFee * 3 + networkFee * 12); }); it('Check operator earnings and cluster balance when reducing operator fee"', async () => { @@ -178,7 +179,7 @@ describe('Balance Tests', () => { await utils.progressBlocks(2); - expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee + newFee * 4); + expect(await ssvViews.getOperatorEarnings(1)).to.equal(helpers.CONFIG.minimalOperatorFee * 4 + helpers.CONFIG.minimalOperatorFee + newFee * 2); expect(await ssvViews.getBalance(helpers.DB.owners[4].address, cluster1.args.operatorIds, cluster1.args.cluster)).to.equal(minDepositAmount - burnPerBlock - ((helpers.CONFIG.minimalOperatorFee * 3 + networkFee) * 2) - newFee * 2); }); diff --git a/test/validators/others.ts b/test/validators/others.ts new file mode 100644 index 00000000..705e103c --- /dev/null +++ b/test/validators/others.ts @@ -0,0 +1,154 @@ +// Declare imports +import * as helpers from '../helpers/contract-helpers'; +import { expect } from 'chai'; +import { trackGas, GasGroup } from '../helpers/gas-usage'; + +// Declare globals +let ssvNetworkContract: any, minDepositAmount: any, firstCluster: any; + +describe('Other Validator Tests', () => { + beforeEach(async () => { + // Initialize contract + const metadata = (await helpers.initializeContract()); + ssvNetworkContract = metadata.contract; + + minDepositAmount = (helpers.CONFIG.minimalBlocksBeforeLiquidation + 10) * helpers.CONFIG.minimalOperatorFee * 4; + + // Register operators + await helpers.registerOperators(0, 14, helpers.CONFIG.minimalOperatorFee); + + // Register a validator + // cold register + await helpers.coldRegisterValidator(); + + // first validator + await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount); + const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( + helpers.DataGenerator.publicKey(1), + [1, 2, 3, 4], + helpers.DataGenerator.shares(4), + minDepositAmount, + { + validatorCount: 0, + networkFeeIndex: 0, + index: 0, + balance: 0, + active: true + } + ), [GasGroup.REGISTER_VALIDATOR_NEW_STATE]); + firstCluster = register.eventsByName.ValidatorAdded[0].args; + }); + + it('Exiting a validator emits "ValidatorExited"', async () => { + await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + helpers.DataGenerator.publicKey(1), + firstCluster.operatorIds, + )).to.emit(ssvNetworkContract, 'ValidatorExited') + .withArgs(helpers.DB.owners[1].address, firstCluster.operatorIds, helpers.DataGenerator.publicKey(1)); + }); + + it('Exiting a validator gas limit', async () => { + await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + helpers.DataGenerator.publicKey(1), + firstCluster.operatorIds, + ), [GasGroup.VALIDATOR_EXIT]); + }); + + it('Exiting one of the validators in a cluster emits "ValidatorExited"', async () => { + await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount); + await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( + helpers.DataGenerator.publicKey(2), + [1, 2, 3, 4], + helpers.DataGenerator.shares(4), + minDepositAmount, + firstCluster.cluster + )); + + await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + helpers.DataGenerator.publicKey(2), + firstCluster.operatorIds, + )).to.emit(ssvNetworkContract, 'ValidatorExited') + .withArgs(helpers.DB.owners[1].address, firstCluster.operatorIds, helpers.DataGenerator.publicKey(2)); + }); + + it('Exiting a removed validator reverts "ValidatorDoesNotExist"', async () => { + await ssvNetworkContract.connect(helpers.DB.owners[1]).removeValidator( + helpers.DataGenerator.publicKey(1), + firstCluster.operatorIds, + firstCluster.cluster + ); + + await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + helpers.DataGenerator.publicKey(1), + firstCluster.operatorIds + )).to.be.revertedWithCustomError(ssvNetworkContract, 'ValidatorDoesNotExist'); + }); + + it('Exiting a non-existing validator reverts "ValidatorDoesNotExist"', async () => { + await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + helpers.DataGenerator.publicKey(12), + firstCluster.operatorIds + )).to.be.revertedWithCustomError(ssvNetworkContract, 'ValidatorDoesNotExist'); + }); + + it('Exiting a validator with empty operator list reverts "IncorrectValidatorState"', async () => { + await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + helpers.DataGenerator.publicKey(1), + [] + )).to.be.revertedWithCustomError(ssvNetworkContract, 'IncorrectValidatorState'); + }); + + it('Exiting a validator with empty public key reverts "ValidatorDoesNotExist"', async () => { + await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + '0x', + firstCluster.operatorIds + )).to.be.revertedWithCustomError(ssvNetworkContract, 'ValidatorDoesNotExist'); + }); + + it('Exiting a validator using the wrong account reverts "ValidatorDoesNotExist"', async () => { + await expect(ssvNetworkContract.connect(helpers.DB.owners[2]).exitValidator( + helpers.DataGenerator.publicKey(1), + firstCluster.operatorIds + )).to.be.revertedWithCustomError(ssvNetworkContract, 'ValidatorDoesNotExist'); + }); + + it('Exiting a validator with incorrect operators (unsorted list) reverts with "IncorrectValidatorState"', async () => { + await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + helpers.DataGenerator.publicKey(1), + [4, 3, 2, 1] + )).to.be.revertedWithCustomError(ssvNetworkContract, 'IncorrectValidatorState'); + }); + + it('Exiting a validator with incorrect operators (too many operators) reverts with "IncorrectValidatorState"', async () => { + minDepositAmount = (helpers.CONFIG.minimalBlocksBeforeLiquidation + 10) * helpers.CONFIG.minimalOperatorFee * 13; + + await helpers.DB.ssvToken.connect(helpers.DB.owners[2]).approve(ssvNetworkContract.address, minDepositAmount); + const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[2]).registerValidator( + helpers.DataGenerator.publicKey(2), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], + helpers.DataGenerator.shares(13), + minDepositAmount, + { + validatorCount: 0, + networkFeeIndex: 0, + index: 0, + balance: 0, + active: true + } + )); + const secondCluster = register.eventsByName.ValidatorAdded[0].args; + + await expect(ssvNetworkContract.connect(helpers.DB.owners[2]).exitValidator( + helpers.DataGenerator.publicKey(2), + secondCluster.operatorIds, + )).to.emit(ssvNetworkContract, 'ValidatorExited') + .withArgs(helpers.DB.owners[2].address, secondCluster.operatorIds, helpers.DataGenerator.publicKey(2)); + }); + + it('Exiting a validator with incorrect operators reverts with "IncorrectValidatorState"', async () => { + await expect(ssvNetworkContract.connect(helpers.DB.owners[1]).exitValidator( + helpers.DataGenerator.publicKey(1), + [1, 2, 3, 5] + )).to.be.revertedWithCustomError(ssvNetworkContract, 'IncorrectValidatorState'); + }); +}); \ No newline at end of file diff --git a/test/validators/register.ts b/test/validators/register.ts index 894f19cd..52f5f9c0 100644 --- a/test/validators/register.ts +++ b/test/validators/register.ts @@ -20,7 +20,6 @@ describe('Register Validator Tests', () => { minDepositAmount = (helpers.CONFIG.minimalBlocksBeforeLiquidation + 2) * helpers.CONFIG.minimalOperatorFee * 13; // cold register - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[6].address, false, true); await helpers.DB.ssvToken.connect(helpers.DB.owners[6]).approve(helpers.DB.ssvNetwork.contract.address, '1000000000000000'); cluster1 = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[6]).registerValidator( helpers.DataGenerator.publicKey(90), @@ -35,8 +34,6 @@ describe('Register Validator Tests', () => { active: true } )); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, true, true); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[0].address, true, true); }); it('Register validator with 4 operators emits "ValidatorAdded"', async () => { @@ -132,8 +129,6 @@ describe('Register Validator Tests', () => { args.cluster ), [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER]); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[2].address, false, true); - await helpers.DB.ssvToken.connect(helpers.DB.owners[2]).approve(ssvNetworkContract.address, minDepositAmount); await trackGas(ssvNetworkContract.connect(helpers.DB.owners[2]).registerValidator( helpers.DataGenerator.publicKey(4), @@ -250,8 +245,6 @@ describe('Register Validator Tests', () => { args.cluster ), [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_7]); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[2].address, false, true); - await helpers.DB.ssvToken.connect(helpers.DB.owners[2]).approve(ssvNetworkContract.address, minDepositAmount); await trackGas(ssvNetworkContract.connect(helpers.DB.owners[2]).registerValidator( helpers.DataGenerator.publicKey(4), @@ -368,8 +361,6 @@ describe('Register Validator Tests', () => { args.cluster ), [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_10]); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[2].address, false, true); - await helpers.DB.ssvToken.connect(helpers.DB.owners[2]).approve(ssvNetworkContract.address, minDepositAmount); await trackGas(ssvNetworkContract.connect(helpers.DB.owners[2]).registerValidator( helpers.DataGenerator.publicKey(4), @@ -486,8 +477,6 @@ describe('Register Validator Tests', () => { args.cluster ), [GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_13]); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[2].address, false, true); - await helpers.DB.ssvToken.connect(helpers.DB.owners[2]).approve(ssvNetworkContract.address, minDepositAmount); await trackGas(ssvNetworkContract.connect(helpers.DB.owners[2]).registerValidator( helpers.DataGenerator.publicKey(4), @@ -795,8 +784,6 @@ describe('Register Validator Tests', () => { await ssvNetworkContract.connect(helpers.DB.owners[1]).setOperatorWhitelist(operatorId, helpers.DB.owners[3].address); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[3].address, false, true); - await helpers.DB.ssvToken.connect(helpers.DB.owners[3]).approve(ssvNetworkContract.address, minDepositAmount); await expect(ssvNetworkContract.connect(helpers.DB.owners[3]).registerValidator( helpers.DataGenerator.publicKey(1), diff --git a/test/validators/remove.ts b/test/validators/remove.ts index 6cc23b45..dbae9393 100644 --- a/test/validators/remove.ts +++ b/test/validators/remove.ts @@ -22,7 +22,6 @@ describe('Remove Validator Tests', () => { // cold register await helpers.coldRegisterValidator(); - await ssvNetworkContract.setRegisterAuth(helpers.DB.owners[1].address, false, true); // first validator await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount); const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( @@ -59,7 +58,7 @@ describe('Remove Validator Tests', () => { )).to.emit(ssvNetworkContract, 'ValidatorRemoved'); }); - it('Remove validator gas limit', async () => { + it('Remove validator gas limit (4 operators cluster)', async () => { await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).removeValidator( helpers.DataGenerator.publicKey(1), firstCluster.operatorIds, @@ -67,6 +66,81 @@ describe('Remove Validator Tests', () => { ), [GasGroup.REMOVE_VALIDATOR]); }); + it('Remove validator gas limit (7 operators cluster)', async () => { + await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount * 2); + + const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( + helpers.DataGenerator.publicKey(2), + [1, 2, 3, 4, 5, 6, 7], + helpers.DataGenerator.shares(7), + minDepositAmount * 2, + { + validatorCount: 0, + networkFeeIndex: 0, + index: 0, + balance: 0, + active: true + } + )); + firstCluster = register.eventsByName.ValidatorAdded[0].args; + + await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).removeValidator( + helpers.DataGenerator.publicKey(2), + firstCluster.operatorIds, + firstCluster.cluster + ), [GasGroup.REMOVE_VALIDATOR_7]); + }); + + it('Remove validator gas limit (10 operators cluster)', async () => { + await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount * 3); + + const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( + helpers.DataGenerator.publicKey(2), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + helpers.DataGenerator.shares(10), + minDepositAmount * 3, + { + validatorCount: 0, + networkFeeIndex: 0, + index: 0, + balance: 0, + active: true + } + )); + firstCluster = register.eventsByName.ValidatorAdded[0].args; + + await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).removeValidator( + helpers.DataGenerator.publicKey(2), + firstCluster.operatorIds, + firstCluster.cluster + ), [GasGroup.REMOVE_VALIDATOR_10]); + }); + + it('Remove validator gas limit (13 operators cluster)', async () => { + await helpers.DB.ssvToken.connect(helpers.DB.owners[1]).approve(ssvNetworkContract.address, minDepositAmount * 4); + + const register = await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).registerValidator( + helpers.DataGenerator.publicKey(2), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], + helpers.DataGenerator.shares(10), + minDepositAmount * 4, + { + validatorCount: 0, + networkFeeIndex: 0, + index: 0, + balance: 0, + active: true + } + )); + firstCluster = register.eventsByName.ValidatorAdded[0].args; + + await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).removeValidator( + helpers.DataGenerator.publicKey(2), + firstCluster.operatorIds, + firstCluster.cluster + ), [GasGroup.REMOVE_VALIDATOR_13]); + }); + it('Remove validator with a removed operator in the cluster', async () => { await trackGas(ssvNetworkContract.removeOperator(1), [GasGroup.REMOVE_OPERATOR_WITH_WITHDRAW]); await trackGas(ssvNetworkContract.connect(helpers.DB.owners[1]).removeValidator(