From 29022d7c4628420efe5fed0560e5269db07d9892 Mon Sep 17 00:00:00 2001 From: Dmitri Tsumak Date: Tue, 28 Nov 2023 15:54:49 +0400 Subject: [PATCH] V3 migration (#128) * Implement migration to V3 vault * Fix comment * Fix linting errors * Reduce parallelism * Remove accounting for fees escrow rewards * Fix linter * Add v3 implementations * Add implementations to networks --- .circleci/config.yml | 4 +- .openzeppelin/goerli.json | 429 ++++++++ .openzeppelin/mainnet.json | 904 ++++++++++++++++ abi/FeesEscrow.json | 48 + abi/IFeesEscrow.json | 28 + abi/IOracles.json | 153 --- abi/IPool.json | 317 +----- abi/IRewardEthToken.json | 68 +- abi/IStakedEthToken.json | 28 +- abi/Oracles.json | 153 --- abi/Pool.json | 336 +----- abi/PoolValidators.json | 597 ----------- abi/RewardEthToken.json | 84 +- abi/StakedEthToken.json | 36 +- contracts/Oracles.sol | 102 -- contracts/interfaces/IEthGenesisVault.sol | 19 + contracts/interfaces/IOracles.sol | 62 -- contracts/interfaces/IPool.sol | 128 +-- contracts/interfaces/IRewardEthToken.sol | 29 +- contracts/interfaces/IStakedEthToken.sol | 10 +- contracts/mocks/MulticallMock.sol | 64 +- contracts/mocks/VaultMock.sol | 27 + contracts/pool/Pool.sol | 208 +--- contracts/pool/PoolValidators.sol | 147 --- contracts/tokens/RewardEthToken.sol | 101 +- contracts/tokens/StakedEthToken.sol | 18 +- deployments/index.js | 79 +- deployments/settings.js | 4 +- hardhat.config.js | 3 +- networks/goerli.md | 26 + networks/mainnet.md | 20 + test/MerkleDistributor.test.js | 1172 ++++++++++----------- test/oracles/Oracles.test.js | 374 +------ test/pool/FeesEscrow.test.js | 40 +- test/pool/PoolEscrow.test.js | 9 +- test/pool/PoolValidators.test.js | 495 --------- test/pool/settings.test.js | 204 ---- test/pool/stake.test.js | 675 ------------ test/tokens/RewardEthToken.test.js | 404 +++++-- test/tokens/StakedEthToken.test.js | 131 +-- test/tokens/toggleRewards.test.js | 51 +- test/utils.js | 120 +-- 42 files changed, 2973 insertions(+), 4934 deletions(-) mode change 100755 => 100644 .openzeppelin/goerli.json create mode 100644 abi/FeesEscrow.json create mode 100644 abi/IFeesEscrow.json delete mode 100644 abi/PoolValidators.json create mode 100644 contracts/interfaces/IEthGenesisVault.sol create mode 100644 contracts/mocks/VaultMock.sol delete mode 100644 contracts/pool/PoolValidators.sol delete mode 100644 test/pool/PoolValidators.test.js delete mode 100644 test/pool/settings.test.js delete mode 100644 test/pool/stake.test.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 2738f086..9a113fb1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,7 +58,7 @@ jobs: test: executor: default resource_class: large - parallelism: 16 + parallelism: 14 steps: - checkout - attach_workspace: @@ -103,7 +103,7 @@ jobs: coverage: executor: default resource_class: large - parallelism: 16 + parallelism: 14 steps: - checkout - attach_workspace: diff --git a/.openzeppelin/goerli.json b/.openzeppelin/goerli.json old mode 100755 new mode 100644 index 6b2608c0..6112d982 --- a/.openzeppelin/goerli.json +++ b/.openzeppelin/goerli.json @@ -2226,6 +2226,435 @@ } } } + }, + "82a45df0c633ffe8473c8b411011d9c8b6510b4a91cbab50a7480a050a3bee22": { + "address": "0x930b1DF55f1775eee2f7d527e61eBDE7d421F1Ee", + "txHash": "0xe089365b5b49ff99a3530fa1e96f247ff741f0eaa82665fa52764622af3b0e25", + "layout": { + "storage": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:25" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:30" + }, + { + "contract": "ContextUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:31" + }, + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:28" + }, + { + "contract": "PausableUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:96" + }, + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)39_storage)", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "contract": "AccessControlUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:225" + }, + { + "contract": "Pool", + "label": "activatedValidators", + "type": "t_uint256", + "src": "contracts/pool/Pool.sol:22" + }, + { + "contract": "Pool", + "label": "withdrawalCredentials", + "type": "t_bytes32", + "src": "contracts/pool/Pool.sol:25" + }, + { + "contract": "Pool", + "label": "validatorRegistration", + "type": "t_contract(IDepositContract)5527", + "src": "contracts/pool/Pool.sol:28" + }, + { + "contract": "Pool", + "label": "stakedEthToken", + "type": "t_contract(IStakedEthToken)6423", + "src": "contracts/pool/Pool.sol:31" + }, + { + "contract": "Pool", + "label": "validators", + "type": "t_contract(IPoolValidators)6147", + "src": "contracts/pool/Pool.sol:34" + }, + { + "contract": "Pool", + "label": "oracles", + "type": "t_address", + "src": "contracts/pool/Pool.sol:37" + }, + { + "contract": "Pool", + "label": "activations", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_uint256))", + "src": "contracts/pool/Pool.sol:40" + }, + { + "contract": "Pool", + "label": "pendingValidators", + "type": "t_uint256", + "src": "contracts/pool/Pool.sol:43" + }, + { + "contract": "Pool", + "label": "minActivatingDeposit", + "type": "t_uint256", + "src": "contracts/pool/Pool.sol:46" + }, + { + "contract": "Pool", + "label": "pendingValidatorsLimit", + "type": "t_uint256", + "src": "contracts/pool/Pool.sol:49" + } + ], + "types": { + "t_uint256": { + "label": "uint256" + }, + "t_bytes32": { + "label": "bytes32" + }, + "t_contract(IDepositContract)5527": { + "label": "contract IDepositContract" + }, + "t_contract(IStakedEthToken)6423": { + "label": "contract IStakedEthToken" + }, + "t_contract(IPoolValidators)6147": { + "label": "contract IPoolValidators" + }, + "t_address": { + "label": "address" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_uint256))": { + "label": "mapping(address => mapping(uint256 => uint256))" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)" + }, + "t_mapping(t_bytes32,t_struct(RoleData)39_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)" + }, + "t_struct(RoleData)39_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_struct(AddressSet)2276_storage" + }, + { + "label": "adminRole", + "type": "t_bytes32" + } + ] + }, + "t_struct(AddressSet)2276_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)2011_storage" + } + ] + }, + "t_struct(Set)2011_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]" + }, + "t_bool": { + "label": "bool" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]" + } + } + } + }, + "1931038781cafac923acd1494ace3cdb39685610a72856a264a9ab2aa98d45b2": { + "address": "0x37a53715C8229BF0D811f03a1F0EC74927A87252", + "txHash": "0xcedc6b0b7b1ca93c32b7bd253d2ce05eb2e5ca32b097ae3e074dbf4d1fdbc80c", + "layout": { + "storage": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:25" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:30" + }, + { + "contract": "ContextUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:31" + }, + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:28" + }, + { + "contract": "PausableUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:96" + }, + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)39_storage)", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "contract": "AccessControlUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:225" + }, + { + "contract": "ERC20Upgradeable", + "label": "_allowances", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "src": "contracts/tokens/ERC20Upgradeable.sol:38" + }, + { + "contract": "ERC20Upgradeable", + "label": "_name", + "type": "t_string_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:40" + }, + { + "contract": "ERC20Upgradeable", + "label": "_symbol", + "type": "t_string_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:41" + }, + { + "contract": "ERC20Upgradeable", + "label": "_decimals", + "type": "t_uint8", + "src": "contracts/tokens/ERC20Upgradeable.sol:42" + }, + { + "contract": "ERC20Upgradeable", + "label": "__gap", + "type": "t_array(t_uint256)44_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:225" + }, + { + "contract": "EIP712Upgradeable", + "label": "_HASHED_NAME", + "type": "t_bytes32", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:27" + }, + { + "contract": "EIP712Upgradeable", + "label": "_HASHED_VERSION", + "type": "t_bytes32", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:28" + }, + { + "contract": "EIP712Upgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:120" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "_nonces", + "type": "t_mapping(t_address,t_struct(Counter)1960_storage)", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:26" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "_PERMIT_TYPEHASH", + "type": "t_bytes32", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:29" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:88" + }, + { + "contract": "StakedEthToken", + "label": "totalDeposits", + "type": "t_uint256", + "src": "contracts/tokens/StakedEthToken.sol:20" + }, + { + "contract": "StakedEthToken", + "label": "deposits", + "type": "t_mapping(t_address,t_uint256)", + "src": "contracts/tokens/StakedEthToken.sol:23" + }, + { + "contract": "StakedEthToken", + "label": "pool", + "type": "t_address", + "src": "contracts/tokens/StakedEthToken.sol:26" + }, + { + "contract": "StakedEthToken", + "label": "rewardEthToken", + "type": "t_contract(IRewardEthToken)6326", + "src": "contracts/tokens/StakedEthToken.sol:29" + }, + { + "contract": "StakedEthToken", + "label": "distributorPrincipal", + "type": "t_uint256", + "src": "contracts/tokens/StakedEthToken.sol:32" + } + ], + "types": { + "t_uint256": { + "label": "uint256" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)" + }, + "t_address": { + "label": "address" + }, + "t_contract(IRewardEthToken)6326": { + "label": "contract IRewardEthToken" + }, + "t_mapping(t_address,t_struct(Counter)1960_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)" + }, + "t_struct(Counter)1960_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256" + } + ] + }, + "t_bytes32": { + "label": "bytes32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))" + }, + "t_string_storage": { + "label": "string" + }, + "t_uint8": { + "label": "uint8" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]" + }, + "t_mapping(t_bytes32,t_struct(RoleData)39_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)" + }, + "t_struct(RoleData)39_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_struct(AddressSet)2276_storage" + }, + { + "label": "adminRole", + "type": "t_bytes32" + } + ] + }, + "t_struct(AddressSet)2276_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)2011_storage" + } + ] + }, + "t_struct(Set)2011_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)" + }, + "t_bool": { + "label": "bool" + } + } + } } } } diff --git a/.openzeppelin/mainnet.json b/.openzeppelin/mainnet.json index 58c476f8..e7092d8f 100644 --- a/.openzeppelin/mainnet.json +++ b/.openzeppelin/mainnet.json @@ -3798,6 +3798,910 @@ } } } + }, + "f2674efbc4266204ce6016513d171dac2b072c00925bbb2ddde47c00f40692cd": { + "address": "0xF0C1670364d4b5c4e9dc8062cDd45068D9c678d6", + "txHash": "0x78502343487f8eba1c8226b9027aa189f3ca1ec97730873bcf924df99f4df0d0", + "layout": { + "storage": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:25" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:30" + }, + { + "contract": "ContextUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:31" + }, + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:28" + }, + { + "contract": "PausableUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:96" + }, + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)39_storage)", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "contract": "AccessControlUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:225" + }, + { + "contract": "Oracles", + "label": "rewardsNonce", + "type": "t_struct(Counter)1781_storage", + "src": "contracts/Oracles.sol:30" + }, + { + "contract": "Oracles", + "label": "validatorsNonce", + "type": "t_struct(Counter)1781_storage", + "src": "contracts/Oracles.sol:33" + }, + { + "contract": "Oracles", + "label": "rewardEthToken", + "type": "t_contract(IRewardEthToken)6147", + "src": "contracts/Oracles.sol:36" + }, + { + "contract": "Oracles", + "label": "pool", + "type": "t_contract(IPool)5812", + "src": "contracts/Oracles.sol:39" + }, + { + "contract": "Oracles", + "label": "poolValidators", + "type": "t_contract(IPoolValidators)5968", + "src": "contracts/Oracles.sol:42" + }, + { + "contract": "Oracles", + "label": "merkleDistributor", + "type": "t_contract(IMerkleDistributor)5534", + "src": "contracts/Oracles.sol:45" + } + ], + "types": { + "t_struct(Counter)1781_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256" + } + ] + }, + "t_uint256": { + "label": "uint256" + }, + "t_contract(IRewardEthToken)6147": { + "label": "contract IRewardEthToken" + }, + "t_contract(IPool)5812": { + "label": "contract IPool" + }, + "t_contract(IPoolValidators)5968": { + "label": "contract IPoolValidators" + }, + "t_contract(IMerkleDistributor)5534": { + "label": "contract IMerkleDistributor" + }, + "t_mapping(t_bytes32,t_struct(RoleData)39_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)" + }, + "t_bytes32": { + "label": "bytes32" + }, + "t_struct(RoleData)39_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_struct(AddressSet)2097_storage" + }, + { + "label": "adminRole", + "type": "t_bytes32" + } + ] + }, + "t_struct(AddressSet)2097_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)1832_storage" + } + ] + }, + "t_struct(Set)1832_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]" + }, + "t_bool": { + "label": "bool" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]" + } + } + } + }, + "ff853ca5989ebdaa3e8e09ac4ab72c65356460f31cb0799f0cbcf330ecf0465f": { + "address": "0x481f28C0D733614aF87897E43d0D52C451799592", + "txHash": "0x44708e829eff49cc466ae6bd1b261a15ba973b6043ad850386177e61a1626fd5", + "layout": { + "storage": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:25" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:30" + }, + { + "contract": "ContextUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:31" + }, + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:28" + }, + { + "contract": "PausableUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:96" + }, + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)39_storage)", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "contract": "AccessControlUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:225" + }, + { + "contract": "Pool", + "label": "activatedValidators", + "type": "t_uint256", + "src": "contracts/pool/Pool.sol:22" + }, + { + "contract": "Pool", + "label": "withdrawalCredentials", + "type": "t_bytes32", + "src": "contracts/pool/Pool.sol:25" + }, + { + "contract": "Pool", + "label": "validatorRegistration", + "type": "t_contract(IDepositContract)5348", + "src": "contracts/pool/Pool.sol:28" + }, + { + "contract": "Pool", + "label": "stakedEthToken", + "type": "t_contract(IStakedEthToken)6244", + "src": "contracts/pool/Pool.sol:31" + }, + { + "contract": "Pool", + "label": "validators", + "type": "t_contract(IPoolValidators)5968", + "src": "contracts/pool/Pool.sol:34" + }, + { + "contract": "Pool", + "label": "oracles", + "type": "t_address", + "src": "contracts/pool/Pool.sol:37" + }, + { + "contract": "Pool", + "label": "activations", + "type": "t_mapping(t_address,t_mapping(t_uint256,t_uint256))", + "src": "contracts/pool/Pool.sol:40" + }, + { + "contract": "Pool", + "label": "pendingValidators", + "type": "t_uint256", + "src": "contracts/pool/Pool.sol:43" + }, + { + "contract": "Pool", + "label": "minActivatingDeposit", + "type": "t_uint256", + "src": "contracts/pool/Pool.sol:46" + }, + { + "contract": "Pool", + "label": "pendingValidatorsLimit", + "type": "t_uint256", + "src": "contracts/pool/Pool.sol:49" + } + ], + "types": { + "t_uint256": { + "label": "uint256" + }, + "t_bytes32": { + "label": "bytes32" + }, + "t_contract(IDepositContract)5348": { + "label": "contract IDepositContract" + }, + "t_contract(IStakedEthToken)6244": { + "label": "contract IStakedEthToken" + }, + "t_contract(IPoolValidators)5968": { + "label": "contract IPoolValidators" + }, + "t_address": { + "label": "address" + }, + "t_mapping(t_address,t_mapping(t_uint256,t_uint256))": { + "label": "mapping(address => mapping(uint256 => uint256))" + }, + "t_mapping(t_uint256,t_uint256)": { + "label": "mapping(uint256 => uint256)" + }, + "t_mapping(t_bytes32,t_struct(RoleData)39_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)" + }, + "t_struct(RoleData)39_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_struct(AddressSet)2097_storage" + }, + { + "label": "adminRole", + "type": "t_bytes32" + } + ] + }, + "t_struct(AddressSet)2097_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)1832_storage" + } + ] + }, + "t_struct(Set)1832_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]" + }, + "t_bool": { + "label": "bool" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]" + } + } + } + }, + "d3e1038f2b8c5aa5ed6be51b93afb94c98735f86a46905e5aa1cd0c14e2b4c98": { + "address": "0x01d34aeE72325F1d4A748f13C2169404523eCEE0", + "txHash": "0x25a6dcc905d486cc76485301185579a697b764202b3faf1daf3b2bff89b539d9", + "layout": { + "storage": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:25" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:30" + }, + { + "contract": "ContextUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:31" + }, + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:28" + }, + { + "contract": "PausableUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:96" + }, + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)39_storage)", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "contract": "AccessControlUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:225" + }, + { + "contract": "ERC20Upgradeable", + "label": "_allowances", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "src": "contracts/tokens/ERC20Upgradeable.sol:38" + }, + { + "contract": "ERC20Upgradeable", + "label": "_name", + "type": "t_string_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:40" + }, + { + "contract": "ERC20Upgradeable", + "label": "_symbol", + "type": "t_string_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:41" + }, + { + "contract": "ERC20Upgradeable", + "label": "_decimals", + "type": "t_uint8", + "src": "contracts/tokens/ERC20Upgradeable.sol:42" + }, + { + "contract": "ERC20Upgradeable", + "label": "__gap", + "type": "t_array(t_uint256)44_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:225" + }, + { + "contract": "EIP712Upgradeable", + "label": "_HASHED_NAME", + "type": "t_bytes32", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:27" + }, + { + "contract": "EIP712Upgradeable", + "label": "_HASHED_VERSION", + "type": "t_bytes32", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:28" + }, + { + "contract": "EIP712Upgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:120" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "_nonces", + "type": "t_mapping(t_address,t_struct(Counter)1781_storage)", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:26" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "_PERMIT_TYPEHASH", + "type": "t_bytes32", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:29" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:88" + }, + { + "contract": "RewardEthToken", + "label": "stakedEthToken", + "type": "t_contract(IStakedEthToken)6244", + "src": "contracts/tokens/RewardEthToken.sol:33" + }, + { + "contract": "RewardEthToken", + "label": "oracles", + "type": "t_address", + "src": "contracts/tokens/RewardEthToken.sol:36" + }, + { + "contract": "RewardEthToken", + "label": "checkpoints", + "type": "t_mapping(t_address,t_struct(Checkpoint)5980_storage)", + "src": "contracts/tokens/RewardEthToken.sol:39" + }, + { + "contract": "RewardEthToken", + "label": "protocolFeeRecipient", + "type": "t_address", + "src": "contracts/tokens/RewardEthToken.sol:42" + }, + { + "contract": "RewardEthToken", + "label": "protocolFee", + "type": "t_uint256", + "src": "contracts/tokens/RewardEthToken.sol:45" + }, + { + "contract": "RewardEthToken", + "label": "totalRewards", + "type": "t_uint128", + "src": "contracts/tokens/RewardEthToken.sol:48" + }, + { + "contract": "RewardEthToken", + "label": "rewardPerToken", + "type": "t_uint128", + "src": "contracts/tokens/RewardEthToken.sol:51" + }, + { + "contract": "RewardEthToken", + "label": "lastUpdateBlockNumber", + "type": "t_uint256", + "src": "contracts/tokens/RewardEthToken.sol:54" + }, + { + "contract": "RewardEthToken", + "label": "merkleDistributor", + "type": "t_address", + "src": "contracts/tokens/RewardEthToken.sol:57" + }, + { + "contract": "RewardEthToken", + "label": "rewardsDisabled", + "type": "t_mapping(t_address,t_bool)", + "src": "contracts/tokens/RewardEthToken.sol:60" + }, + { + "contract": "RewardEthToken", + "label": "feesEscrow", + "type": "t_contract(IFeesEscrow)5377", + "src": "contracts/tokens/RewardEthToken.sol:63" + }, + { + "contract": "RewardEthToken", + "label": "totalPenalty", + "type": "t_uint256", + "src": "contracts/tokens/RewardEthToken.sol:66" + } + ], + "types": { + "t_contract(IStakedEthToken)6244": { + "label": "contract IStakedEthToken" + }, + "t_address": { + "label": "address" + }, + "t_mapping(t_address,t_struct(Checkpoint)5980_storage)": { + "label": "mapping(address => struct IRewardEthToken.Checkpoint)" + }, + "t_struct(Checkpoint)5980_storage": { + "label": "struct IRewardEthToken.Checkpoint", + "members": [ + { + "label": "reward", + "type": "t_uint128" + }, + { + "label": "rewardPerToken", + "type": "t_uint128" + } + ] + }, + "t_uint128": { + "label": "uint128" + }, + "t_uint256": { + "label": "uint256" + }, + "t_mapping(t_address,t_bool)": { + "label": "mapping(address => bool)" + }, + "t_bool": { + "label": "bool" + }, + "t_contract(IFeesEscrow)5377": { + "label": "contract IFeesEscrow" + }, + "t_mapping(t_address,t_struct(Counter)1781_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)" + }, + "t_struct(Counter)1781_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256" + } + ] + }, + "t_bytes32": { + "label": "bytes32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)" + }, + "t_string_storage": { + "label": "string" + }, + "t_uint8": { + "label": "uint8" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]" + }, + "t_mapping(t_bytes32,t_struct(RoleData)39_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)" + }, + "t_struct(RoleData)39_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_struct(AddressSet)2097_storage" + }, + { + "label": "adminRole", + "type": "t_bytes32" + } + ] + }, + "t_struct(AddressSet)2097_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)1832_storage" + } + ] + }, + "t_struct(Set)1832_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)" + } + } + } + }, + "1931038781cafac923acd1494ace3cdb39685610a72856a264a9ab2aa98d45b2": { + "address": "0x82FE8C78CaE0013471179e76224ef89941bAaa75", + "txHash": "0xbb4e9b9cf7909a4fc360b27273855c1dff12f35f76fc3cf7d5ad1ee7e7bda262", + "layout": { + "storage": [ + { + "contract": "Initializable", + "label": "_initialized", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:25" + }, + { + "contract": "Initializable", + "label": "_initializing", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol:30" + }, + { + "contract": "ContextUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:31" + }, + { + "contract": "PausableUpgradeable", + "label": "_paused", + "type": "t_bool", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:28" + }, + { + "contract": "PausableUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol:96" + }, + { + "contract": "AccessControlUpgradeable", + "label": "_roles", + "type": "t_mapping(t_bytes32,t_struct(RoleData)39_storage)", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:61" + }, + { + "contract": "AccessControlUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:225" + }, + { + "contract": "ERC20Upgradeable", + "label": "_allowances", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))", + "src": "contracts/tokens/ERC20Upgradeable.sol:38" + }, + { + "contract": "ERC20Upgradeable", + "label": "_name", + "type": "t_string_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:40" + }, + { + "contract": "ERC20Upgradeable", + "label": "_symbol", + "type": "t_string_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:41" + }, + { + "contract": "ERC20Upgradeable", + "label": "_decimals", + "type": "t_uint8", + "src": "contracts/tokens/ERC20Upgradeable.sol:42" + }, + { + "contract": "ERC20Upgradeable", + "label": "__gap", + "type": "t_array(t_uint256)44_storage", + "src": "contracts/tokens/ERC20Upgradeable.sol:225" + }, + { + "contract": "EIP712Upgradeable", + "label": "_HASHED_NAME", + "type": "t_bytes32", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:27" + }, + { + "contract": "EIP712Upgradeable", + "label": "_HASHED_VERSION", + "type": "t_bytes32", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:28" + }, + { + "contract": "EIP712Upgradeable", + "label": "__gap", + "type": "t_array(t_uint256)50_storage", + "src": "@openzeppelin/contracts-upgradeable/drafts/EIP712Upgradeable.sol:120" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "_nonces", + "type": "t_mapping(t_address,t_struct(Counter)1781_storage)", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:26" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "_PERMIT_TYPEHASH", + "type": "t_bytes32", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:29" + }, + { + "contract": "ERC20PermitUpgradeable", + "label": "__gap", + "type": "t_array(t_uint256)49_storage", + "src": "contracts/tokens/ERC20PermitUpgradeable.sol:88" + }, + { + "contract": "StakedEthToken", + "label": "totalDeposits", + "type": "t_uint256", + "src": "contracts/tokens/StakedEthToken.sol:20" + }, + { + "contract": "StakedEthToken", + "label": "deposits", + "type": "t_mapping(t_address,t_uint256)", + "src": "contracts/tokens/StakedEthToken.sol:23" + }, + { + "contract": "StakedEthToken", + "label": "pool", + "type": "t_address", + "src": "contracts/tokens/StakedEthToken.sol:26" + }, + { + "contract": "StakedEthToken", + "label": "rewardEthToken", + "type": "t_contract(IRewardEthToken)6147", + "src": "contracts/tokens/StakedEthToken.sol:29" + }, + { + "contract": "StakedEthToken", + "label": "distributorPrincipal", + "type": "t_uint256", + "src": "contracts/tokens/StakedEthToken.sol:32" + } + ], + "types": { + "t_uint256": { + "label": "uint256" + }, + "t_mapping(t_address,t_uint256)": { + "label": "mapping(address => uint256)" + }, + "t_address": { + "label": "address" + }, + "t_contract(IRewardEthToken)6147": { + "label": "contract IRewardEthToken" + }, + "t_mapping(t_address,t_struct(Counter)1781_storage)": { + "label": "mapping(address => struct CountersUpgradeable.Counter)" + }, + "t_struct(Counter)1781_storage": { + "label": "struct CountersUpgradeable.Counter", + "members": [ + { + "label": "_value", + "type": "t_uint256" + } + ] + }, + "t_bytes32": { + "label": "bytes32" + }, + "t_array(t_uint256)49_storage": { + "label": "uint256[49]" + }, + "t_array(t_uint256)50_storage": { + "label": "uint256[50]" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "label": "mapping(address => mapping(address => uint256))" + }, + "t_string_storage": { + "label": "string" + }, + "t_uint8": { + "label": "uint8" + }, + "t_array(t_uint256)44_storage": { + "label": "uint256[44]" + }, + "t_mapping(t_bytes32,t_struct(RoleData)39_storage)": { + "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)" + }, + "t_struct(RoleData)39_storage": { + "label": "struct AccessControlUpgradeable.RoleData", + "members": [ + { + "label": "members", + "type": "t_struct(AddressSet)2097_storage" + }, + { + "label": "adminRole", + "type": "t_bytes32" + } + ] + }, + "t_struct(AddressSet)2097_storage": { + "label": "struct EnumerableSetUpgradeable.AddressSet", + "members": [ + { + "label": "_inner", + "type": "t_struct(Set)1832_storage" + } + ] + }, + "t_struct(Set)1832_storage": { + "label": "struct EnumerableSetUpgradeable.Set", + "members": [ + { + "label": "_values", + "type": "t_array(t_bytes32)dyn_storage" + }, + { + "label": "_indexes", + "type": "t_mapping(t_bytes32,t_uint256)" + } + ] + }, + "t_array(t_bytes32)dyn_storage": { + "label": "bytes32[]" + }, + "t_mapping(t_bytes32,t_uint256)": { + "label": "mapping(bytes32 => uint256)" + }, + "t_bool": { + "label": "bool" + } + } + } } } } diff --git a/abi/FeesEscrow.json b/abi/FeesEscrow.json new file mode 100644 index 00000000..cfaa51a6 --- /dev/null +++ b/abi/FeesEscrow.json @@ -0,0 +1,48 @@ +[ + { + "inputs": [ + { + "internalType": "contract IPool", + "name": "_pool", + "type": "address" + }, + { + "internalType": "address", + "name": "_rewardEthToken", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "FeesTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "transferToPool", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/abi/IFeesEscrow.json b/abi/IFeesEscrow.json new file mode 100644 index 00000000..de112b4a --- /dev/null +++ b/abi/IFeesEscrow.json @@ -0,0 +1,28 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "FeesTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "transferToPool", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/IOracles.json b/abi/IOracles.json index 3fc4aca9..fce4c531 100644 --- a/abi/IOracles.json +++ b/abi/IOracles.json @@ -62,68 +62,6 @@ "name": "OracleRemoved", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "address[]", - "name": "oracles", - "type": "address[]" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - } - ], - "name": "RegisterValidatorsVoteSubmitted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "oracle", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "totalRewards", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "activatedValidators", - "type": "uint256" - } - ], - "name": "RewardsVoteSubmitted", - "type": "event" - }, { "inputs": [ { @@ -150,19 +88,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "currentValidatorsNonce", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "isMerkleRootVoting", @@ -195,61 +120,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "withdrawalCredentials", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "depositDataRoot", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "publicKey", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "internalType": "struct IPoolValidators.DepositData[]", - "name": "depositData", - "type": "tuple[]" - }, - { - "internalType": "bytes32[][]", - "name": "merkleProofs", - "type": "bytes32[][]" - }, - { - "internalType": "bytes32", - "name": "validatorsDepositRoot", - "type": "bytes32" - }, - { - "internalType": "bytes[]", - "name": "signatures", - "type": "bytes[]" - } - ], - "name": "registerValidators", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -285,28 +155,5 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "totalRewards", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "activatedValidators", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "signatures", - "type": "bytes[]" - } - ], - "name": "submitRewards", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" } ] diff --git a/abi/IPool.json b/abi/IPool.json index fa680b90..25c43478 100644 --- a/abi/IPool.json +++ b/abi/IPool.json @@ -171,143 +171,12 @@ }, { "inputs": [], - "name": "VALIDATOR_TOTAL_DEPOSIT", + "name": "poolEscrow", "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "validatorIndex", - "type": "uint256" - } - ], - "name": "activate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "validatorIndexes", - "type": "uint256[]" - } - ], - "name": "activateMultiple", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "activatedValidators", - "outputs": [ - { - "internalType": "uint256", "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", "type": "address" - }, - { - "internalType": "uint256", - "name": "validatorIndex", - "type": "uint256" - } - ], - "name": "activations", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "validatorIndex", - "type": "uint256" - } - ], - "name": "canActivate", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "minActivatingDeposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pendingValidators", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pendingValidatorsLimit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" } ], "stateMutability": "view", @@ -320,191 +189,11 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "withdrawalCredentials", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "depositDataRoot", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "publicKey", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "internalType": "struct IPoolValidators.DepositData", - "name": "depositData", - "type": "tuple" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newActivatedValidators", - "type": "uint256" - } - ], - "name": "setActivatedValidators", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newMinActivatingDeposit", - "type": "uint256" - } - ], - "name": "setMinActivatingDeposit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newPendingValidatorsLimit", - "type": "uint256" - } - ], - "name": "setPendingValidatorsLimit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], - "name": "stake", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "stakeOnBehalf", + "name": "transferToPoolEscrow", "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "partner", - "type": "address" - } - ], - "name": "stakeWithPartner", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "partner", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "stakeWithPartnerOnBehalf", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "referrer", - "type": "address" - } - ], - "name": "stakeWithReferrer", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "referrer", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "stakeWithReferrerOnBehalf", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "validatorRegistration", - "outputs": [ - { - "internalType": "contract IDepositContract", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "withdrawalCredentials", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" } ] diff --git a/abi/IRewardEthToken.json b/abi/IRewardEthToken.json index 97392bd8..64ba6cd8 100644 --- a/abi/IRewardEthToken.json +++ b/abi/IRewardEthToken.json @@ -266,6 +266,29 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reward", + "type": "uint256" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "protocolFee", @@ -368,6 +391,32 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalPenalty", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "totalRewards", @@ -498,14 +547,27 @@ { "inputs": [ { - "internalType": "uint256", - "name": "newTotalRewards", - "type": "uint256" + "internalType": "int256", + "name": "rewardsDelta", + "type": "int256" } ], "name": "updateTotalRewards", "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" } ] diff --git a/abi/IStakedEthToken.json b/abi/IStakedEthToken.json index 6bb4ceb8..2ebe37c8 100644 --- a/abi/IStakedEthToken.json +++ b/abi/IStakedEthToken.json @@ -116,19 +116,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "distributorPrincipal", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -142,11 +129,24 @@ "type": "uint256" } ], - "name": "mint", + "name": "burn", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "distributorPrincipal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/abi/Oracles.json b/abi/Oracles.json index 6ebf6adc..cfaed5f9 100644 --- a/abi/Oracles.json +++ b/abi/Oracles.json @@ -75,68 +75,6 @@ "name": "Paused", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "address[]", - "name": "oracles", - "type": "address[]" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - } - ], - "name": "RegisterValidatorsVoteSubmitted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "oracle", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "totalRewards", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "activatedValidators", - "type": "uint256" - } - ], - "name": "RewardsVoteSubmitted", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -316,19 +254,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "currentValidatorsNonce", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -523,61 +448,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "withdrawalCredentials", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "depositDataRoot", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "publicKey", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "internalType": "struct IPoolValidators.DepositData[]", - "name": "depositData", - "type": "tuple[]" - }, - { - "internalType": "bytes32[][]", - "name": "merkleProofs", - "type": "bytes32[][]" - }, - { - "internalType": "bytes32", - "name": "validatorsDepositRoot", - "type": "bytes32" - }, - { - "internalType": "bytes[]", - "name": "signatures", - "type": "bytes[]" - } - ], - "name": "registerValidators", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -676,29 +546,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "totalRewards", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "activatedValidators", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "signatures", - "type": "bytes[]" - } - ], - "name": "submitRewards", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "unpause", diff --git a/abi/Pool.json b/abi/Pool.json index d5600711..aa0fdf1e 100644 --- a/abi/Pool.json +++ b/abi/Pool.json @@ -1,4 +1,15 @@ [ + { + "inputs": [ + { + "internalType": "address", + "name": "_poolEscrow", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, { "anonymous": false, "inputs": [ @@ -296,92 +307,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "VALIDATOR_TOTAL_DEPOSIT", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "validatorIndex", - "type": "uint256" - } - ], - "name": "activate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256[]", - "name": "validatorIndexes", - "type": "uint256[]" - } - ], - "name": "activateMultiple", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "activatedValidators", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "activations", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -408,25 +333,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "validatorIndex", - "type": "uint256" - } - ], - "name": "canActivate", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -569,19 +475,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "minActivatingDeposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "pause", @@ -604,25 +497,12 @@ }, { "inputs": [], - "name": "pendingValidators", + "name": "poolEscrow", "outputs": [ { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pendingValidatorsLimit", - "outputs": [ - { - "internalType": "uint256", + "internalType": "address", "name": "", - "type": "uint256" + "type": "address" } ], "stateMutability": "view", @@ -635,46 +515,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "withdrawalCredentials", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "depositDataRoot", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "publicKey", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "internalType": "struct IPoolValidators.DepositData", - "name": "depositData", - "type": "tuple" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -737,125 +577,11 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newActivatedValidators", - "type": "uint256" - } - ], - "name": "setActivatedValidators", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newMinActivatingDeposit", - "type": "uint256" - } - ], - "name": "setMinActivatingDeposit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "newPendingValidatorsLimit", - "type": "uint256" - } - ], - "name": "setPendingValidatorsLimit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], - "name": "stake", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "stakeOnBehalf", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "partner", - "type": "address" - } - ], - "name": "stakeWithPartner", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "partner", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "stakeWithPartnerOnBehalf", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "referrer", - "type": "address" - } - ], - "name": "stakeWithReferrer", + "name": "transferToPoolEscrow", "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "referrer", - "type": "address" - }, - { - "internalType": "address", - "name": "recipient", - "type": "address" - } - ], - "name": "stakeWithReferrerOnBehalf", - "outputs": [], - "stateMutability": "payable", + "stateMutability": "nonpayable", "type": "function" }, { @@ -864,35 +590,5 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" - }, - { - "inputs": [], - "name": "validatorRegistration", - "outputs": [ - { - "internalType": "contract IDepositContract", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "withdrawalCredentials", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "stateMutability": "payable", - "type": "receive" } ] diff --git a/abi/PoolValidators.json b/abi/PoolValidators.json deleted file mode 100644 index 92d3b9f7..00000000 --- a/abi/PoolValidators.json +++ /dev/null @@ -1,597 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "depositDataMerkleRoot", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "string", - "name": "depositDataMerkleProofs", - "type": "string" - } - ], - "name": "OperatorAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "OperatorCommitted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "OperatorRemoved", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Paused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "previousAdminRole", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "newAdminRole", - "type": "bytes32" - } - ], - "name": "RoleAdminChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleGranted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RoleRevoked", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Unpaused", - "type": "event" - }, - { - "inputs": [], - "name": "DEFAULT_ADMIN_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "PAUSER_ROLE", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "addAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "depositDataMerkleRoot", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "depositDataMerkleProofs", - "type": "string" - } - ], - "name": "addOperator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "addPauser", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "commitOperator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "getOperator", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - } - ], - "name": "getRoleAdmin", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } - ], - "name": "getRoleMember", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - } - ], - "name": "getRoleMemberCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "grantRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "hasRole", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_admin", - "type": "address" - }, - { - "internalType": "address", - "name": "_pool", - "type": "address" - }, - { - "internalType": "address", - "name": "_oracles", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "isAdmin", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "isPauser", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "isValidatorRegistered", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "withdrawalCredentials", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "depositDataRoot", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "publicKey", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "internalType": "struct IPoolValidators.DepositData", - "name": "depositData", - "type": "tuple" - }, - { - "internalType": "bytes32[]", - "name": "merkleProof", - "type": "bytes32[]" - } - ], - "name": "registerValidator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "removeAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_operator", - "type": "address" - } - ], - "name": "removeOperator", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "removePauser", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "renounceRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "role", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "revokeRole", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/abi/RewardEthToken.json b/abi/RewardEthToken.json index 60249740..1195a008 100644 --- a/abi/RewardEthToken.json +++ b/abi/RewardEthToken.json @@ -1,4 +1,20 @@ [ + { + "inputs": [ + { + "internalType": "address", + "name": "_vault", + "type": "address" + }, + { + "internalType": "address", + "name": "_pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, { "anonymous": false, "inputs": [ @@ -635,6 +651,29 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "principal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reward", + "type": "uint256" + } + ], + "name": "migrate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "name", @@ -907,6 +946,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalPenalty", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "totalRewards", @@ -1044,14 +1109,27 @@ { "inputs": [ { - "internalType": "uint256", - "name": "newTotalRewards", - "type": "uint256" + "internalType": "int256", + "name": "rewardsDelta", + "type": "int256" } ], "name": "updateTotalRewards", "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" } ] diff --git a/abi/StakedEthToken.json b/abi/StakedEthToken.json index 7b8838e6..8d48d530 100644 --- a/abi/StakedEthToken.json +++ b/abi/StakedEthToken.json @@ -282,6 +282,24 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "decimals", @@ -498,24 +516,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "name", diff --git a/contracts/Oracles.sol b/contracts/Oracles.sol index d2c4fd99..24371715 100644 --- a/contracts/Oracles.sol +++ b/contracts/Oracles.sol @@ -59,13 +59,6 @@ contract Oracles is IOracles, OwnablePausableUpgradeable { return rewardsNonce.current(); } - /** - * @dev See {IOracles-currentValidatorsNonce}. - */ - function currentValidatorsNonce() external override view returns (uint256) { - return validatorsNonce.current(); - } - /** * @dev See {IOracles-isOracle}. */ @@ -107,50 +100,6 @@ contract Oracles is IOracles, OwnablePausableUpgradeable { return totalOracles >= signaturesCount && signaturesCount.mul(3) > totalOracles.mul(2); } - /** - * @dev See {IOracles-submitRewards}. - */ - function submitRewards( - uint256 totalRewards, - uint256 activatedValidators, - bytes[] calldata signatures - ) - external override onlyOracle whenNotPaused - { - require(isEnoughSignatures(signatures.length), "Oracles: invalid number of signatures"); - - // calculate candidate ID hash - uint256 nonce = rewardsNonce.current(); - bytes32 candidateId = ECDSAUpgradeable.toEthSignedMessageHash( - keccak256(abi.encode(nonce, activatedValidators, totalRewards)) - ); - - // check signatures and calculate number of submitted oracle votes - address[] memory signedOracles = new address[](signatures.length); - for (uint256 i = 0; i < signatures.length; i++) { - bytes memory signature = signatures[i]; - address signer = ECDSAUpgradeable.recover(candidateId, signature); - require(hasRole(ORACLE_ROLE, signer), "Oracles: invalid signer"); - - for (uint256 j = 0; j < i; j++) { - require(signedOracles[j] != signer, "Oracles: repeated signature"); - } - signedOracles[i] = signer; - emit RewardsVoteSubmitted(msg.sender, signer, nonce, totalRewards, activatedValidators); - } - - // increment nonce for future signatures - rewardsNonce.increment(); - - // update total rewards - rewardEthToken.updateTotalRewards(totalRewards); - - // update activated validators - if (activatedValidators != pool.activatedValidators()) { - pool.setActivatedValidators(activatedValidators); - } - } - /** * @dev See {IOracles-submitMerkleRoot}. */ @@ -190,55 +139,4 @@ contract Oracles is IOracles, OwnablePausableUpgradeable { // update merkle root merkleDistributor.setMerkleRoot(merkleRoot, merkleProofs); } - - /** - * @dev See {IOracles-registerValidators}. - */ - function registerValidators( - IPoolValidators.DepositData[] calldata depositData, - bytes32[][] calldata merkleProofs, - bytes32 validatorsDepositRoot, - bytes[] calldata signatures - ) - external override onlyOracle whenNotPaused - { - require( - pool.validatorRegistration().get_deposit_root() == validatorsDepositRoot, - "Oracles: invalid validators deposit root" - ); - require(isEnoughSignatures(signatures.length), "Oracles: invalid number of signatures"); - - // calculate candidate ID hash - uint256 nonce = validatorsNonce.current(); - bytes32 candidateId = ECDSAUpgradeable.toEthSignedMessageHash( - keccak256(abi.encode(nonce, depositData, validatorsDepositRoot)) - ); - - // check signatures are correct - address[] memory signedOracles = new address[](signatures.length); - for (uint256 i = 0; i < signatures.length; i++) { - bytes memory signature = signatures[i]; - address signer = ECDSAUpgradeable.recover(candidateId, signature); - require(hasRole(ORACLE_ROLE, signer), "Oracles: invalid signer"); - - for (uint256 j = 0; j < i; j++) { - require(signedOracles[j] != signer, "Oracles: repeated signature"); - } - signedOracles[i] = signer; - } - - uint256 depositDataLength = depositData.length; - require(merkleProofs.length == depositDataLength, "Oracles: invalid merkle proofs length"); - - // submit deposit data - for (uint256 i = 0; i < depositDataLength; i++) { - // register validator - poolValidators.registerValidator(depositData[i], merkleProofs[i]); - } - - emit RegisterValidatorsVoteSubmitted(msg.sender, signedOracles, nonce); - - // increment nonce for future registrations - validatorsNonce.increment(); - } } diff --git a/contracts/interfaces/IEthGenesisVault.sol b/contracts/interfaces/IEthGenesisVault.sol new file mode 100644 index 00000000..f2c39b7a --- /dev/null +++ b/contracts/interfaces/IEthGenesisVault.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +pragma solidity 0.7.5; + + +/** + * @title IEthGenesisVault + * @author StakeWise + * @notice Defines the interface for the EthGenesisVault contract + */ +interface IEthGenesisVault { + /** + * @notice Function for migrating from StakeWise v2. Can be called only by RewardEthToken contract. + * @param receiver The address of the receiver + * @param assets The amount of assets migrated + * @return shares The amount of shares minted + */ + function migrate(address receiver, uint256 assets) external returns (uint256 shares); +} diff --git a/contracts/interfaces/IOracles.sol b/contracts/interfaces/IOracles.sol index eed704f4..f3f9c9ce 100644 --- a/contracts/interfaces/IOracles.sol +++ b/contracts/interfaces/IOracles.sol @@ -2,29 +2,12 @@ pragma solidity 0.7.5; -import "./IPoolValidators.sol"; pragma abicoder v2; /** * @dev Interface of the Oracles contract. */ interface IOracles { - /** - * @dev Event for tracking oracle rewards votes. - * @param sender - address of the transaction sender. - * @param oracle - address of the account which submitted vote. - * @param nonce - current nonce. - * @param totalRewards - submitted value of total rewards. - * @param activatedValidators - submitted amount of activated validators. - */ - event RewardsVoteSubmitted( - address indexed sender, - address indexed oracle, - uint256 nonce, - uint256 totalRewards, - uint256 activatedValidators - ); - /** * @dev Event for tracking oracle merkle root votes. * @param sender - address of the transaction sender. @@ -41,18 +24,6 @@ interface IOracles { string merkleProofs ); - /** - * @dev Event for tracking validators registration vote. - * @param sender - address of the transaction sender. - * @param oracles - addresses of the signed oracles. - * @param nonce - validators registration nonce. - */ - event RegisterValidatorsVoteSubmitted( - address indexed sender, - address[] oracles, - uint256 nonce - ); - /** * @dev Event for tracking new or updates oracles. * @param oracle - address of new or updated oracle. @@ -81,11 +52,6 @@ interface IOracles { */ function currentRewardsNonce() external view returns (uint256); - /** - * @dev Function for retrieving current validators nonce. - */ - function currentValidatorsNonce() external view returns (uint256); - /** * @dev Function for adding an oracle role to the account. * Can only be called by an account with an admin role. @@ -100,19 +66,6 @@ interface IOracles { */ function removeOracle(address account) external; - /** - * @dev Function for submitting oracle vote for total rewards. - * The quorum of signatures over the same data is required to submit the new value. - * @param totalRewards - voted total rewards. - * @param activatedValidators - voted amount of activated validators. - * @param signatures - oracles' signatures. - */ - function submitRewards( - uint256 totalRewards, - uint256 activatedValidators, - bytes[] calldata signatures - ) external; - /** * @dev Function for submitting new merkle root. * The quorum of signatures over the same data is required to submit the new value. @@ -125,19 +78,4 @@ interface IOracles { string calldata merkleProofs, bytes[] calldata signatures ) external; - - /** - * @dev Function for submitting registrations of the new validators. - * The quorum of signatures over the same data is required to register. - * @param depositData - an array of deposit data to register. - * @param merkleProofs - an array of hashes to verify whether the every deposit data is part of the merkle root. - * @param validatorsDepositRoot - validators deposit root to protect from malicious operators. - * @param signatures - oracles' signatures. - */ - function registerValidators( - IPoolValidators.DepositData[] calldata depositData, - bytes32[][] calldata merkleProofs, - bytes32 validatorsDepositRoot, - bytes[] calldata signatures - ) external; } diff --git a/contracts/interfaces/IPool.sol b/contracts/interfaces/IPool.sol index 1770946b..ebf6cc45 100644 --- a/contracts/interfaces/IPool.sol +++ b/contracts/interfaces/IPool.sol @@ -1,10 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.7.5; -pragma abicoder v2; import "./IDepositContract.sol"; -import "./IPoolValidators.sol"; /** * @dev Interface of the Pool contract. @@ -71,73 +69,9 @@ interface IPool { event StakedWithReferrer(address indexed referrer, uint256 amount); /** - * @dev Function for getting the total validator deposit. + * @dev Returns PoolEscrow contract address. */ - // solhint-disable-next-line func-name-mixedcase - function VALIDATOR_TOTAL_DEPOSIT() external view returns (uint256); - - /** - * @dev Function for retrieving the total amount of pending validators. - */ - function pendingValidators() external view returns (uint256); - - /** - * @dev Function for retrieving the total amount of activated validators. - */ - function activatedValidators() external view returns (uint256); - - /** - * @dev Function for retrieving the withdrawal credentials used to - * initiate pool validators withdrawal from the beacon chain. - */ - function withdrawalCredentials() external view returns (bytes32); - - /** - * @dev Function for getting the minimal deposit amount considered for the activation. - */ - function minActivatingDeposit() external view returns (uint256); - - /** - * @dev Function for getting the pending validators percent limit. - * When it's exceeded, the deposits will be set for the activation. - */ - function pendingValidatorsLimit() external view returns (uint256); - - /** - * @dev Function for getting the amount of activating deposits. - * @param account - address of the account to get the amount for. - * @param validatorIndex - index of the activated validator. - */ - function activations(address account, uint256 validatorIndex) external view returns (uint256); - - /** - * @dev Function for setting minimal deposit amount considered for the activation period. - * @param newMinActivatingDeposit - new minimal deposit amount considered for the activation. - */ - function setMinActivatingDeposit(uint256 newMinActivatingDeposit) external; - - /** - * @dev Function for changing the total amount of activated validators. - * @param newActivatedValidators - new total amount of activated validators. - */ - function setActivatedValidators(uint256 newActivatedValidators) external; - - /** - * @dev Function for changing pending validators limit. - * @param newPendingValidatorsLimit - new pending validators limit. When it's exceeded, the deposits will be set for the activation. - */ - function setPendingValidatorsLimit(uint256 newPendingValidatorsLimit) external; - - /** - * @dev Function for checking whether validator index can be activated. - * @param validatorIndex - index of the validator to check. - */ - function canActivate(uint256 validatorIndex) external view returns (bool); - - /** - * @dev Function for retrieving the validator registration contract address. - */ - function validatorRegistration() external view returns (IDepositContract); + function poolEscrow() external view returns (address); /** * @dev Function for receiving native tokens without minting sETH. @@ -145,61 +79,7 @@ interface IPool { function receiveFees() external payable; /** - * @dev Function for staking ether to the pool to the different tokens' recipient. - * @param recipient - address of the tokens recipient. - */ - function stakeOnBehalf(address recipient) external payable; - - /** - * @dev Function for staking ether to the pool. - */ - function stake() external payable; - - /** - * @dev Function for staking ether with the partner that will receive the revenue share from the protocol fee. - * @param partner - address of partner who will get the revenue share. - */ - function stakeWithPartner(address partner) external payable; - - /** - * @dev Function for staking ether with the partner that will receive the revenue share from the protocol fee - * and the different tokens' recipient. - * @param partner - address of partner who will get the revenue share. - * @param recipient - address of the tokens recipient. - */ - function stakeWithPartnerOnBehalf(address partner, address recipient) external payable; - - /** - * @dev Function for staking ether with the referrer who will receive the one time bonus. - * @param referrer - address of referrer who will get its referral bonus. - */ - function stakeWithReferrer(address referrer) external payable; - - /** - * @dev Function for staking ether with the referrer who will receive the one time bonus - * and the different tokens' recipient. - * @param referrer - address of referrer who will get its referral bonus. - * @param recipient - address of the tokens recipient. - */ - function stakeWithReferrerOnBehalf(address referrer, address recipient) external payable; - - /** - * @dev Function for minting account's tokens for the specific validator index. - * @param account - account address to activate the tokens for. - * @param validatorIndex - index of the activated validator. - */ - function activate(address account, uint256 validatorIndex) external; - - /** - * @dev Function for minting account's tokens for the specific validator indexes. - * @param account - account address to activate the tokens for. - * @param validatorIndexes - list of activated validator indexes. - */ - function activateMultiple(address account, uint256[] calldata validatorIndexes) external; - - /** - * @dev Function for registering new pool validator registration. - * @param depositData - the deposit data to submit for the validator. + * @dev Function for transferring all ETH accumulated in Pool contract to PoolEscrow contract. */ - function registerValidator(IPoolValidators.DepositData calldata depositData) external; + function transferToPoolEscrow() external; } diff --git a/contracts/interfaces/IRewardEthToken.sol b/contracts/interfaces/IRewardEthToken.sol index 4fee3b8a..9cd202da 100644 --- a/contracts/interfaces/IRewardEthToken.sol +++ b/contracts/interfaces/IRewardEthToken.sol @@ -64,6 +64,21 @@ interface IRewardEthToken is IERC20Upgradeable { */ function protocolFeeRecipient() external view returns (address); + /** + * @dev Function for getting the address of the vault. + */ + function vault() external view returns (address); + + /** + * @dev Function for getting the total assets. + */ + function totalAssets() external view returns (uint256); + + /** + * @dev Function for getting the total penalty. + */ + function totalPenalty() external view returns (uint256); + /** * @dev Function for changing the protocol fee recipient's address. * @param recipient - new protocol fee recipient's address. @@ -131,10 +146,18 @@ interface IRewardEthToken is IERC20Upgradeable { /** * @dev Function for updating validators total rewards. - * Can only be called by Oracles contract. - * @param newTotalRewards - new total rewards. + * Can only be called by Vault contract. + * @param rewardsDelta - the total rewards earned or penalties received. + */ + function updateTotalRewards(int256 rewardsDelta) external; + + /** + * @dev Function for migrating to the StakeWise V3 Vault. + * @param receiver - address of the account the tokens will be assigned to. + * @param principal - amount of sETH2 tokens to migrate. + * @param reward - amount of rETH2 tokens to migrate. */ - function updateTotalRewards(uint256 newTotalRewards) external; + function migrate(address receiver, uint256 principal, uint256 reward) external; /** * @dev Function for claiming rETH2 from the merkle distribution. diff --git a/contracts/interfaces/IStakedEthToken.sol b/contracts/interfaces/IStakedEthToken.sol index 6822a6ca..067b7c7d 100644 --- a/contracts/interfaces/IStakedEthToken.sol +++ b/contracts/interfaces/IStakedEthToken.sol @@ -26,10 +26,10 @@ interface IStakedEthToken is IERC20Upgradeable { function toggleRewards(address account, bool isDisabled) external; /** - * @dev Function for creating `amount` tokens and assigning them to `account`. - * Can only be called by Pool contract. - * @param account - address of the account to assign tokens to. - * @param amount - amount of tokens to assign. + * @dev Function for burning `amount` tokens from `account`. + * Can only be called by RewardEthToken contract. + * @param account - address of the account to burn tokens from. + * @param amount - amount of tokens to burn. */ - function mint(address account, uint256 amount) external; + function burn(address account, uint256 amount) external; } diff --git a/contracts/mocks/MulticallMock.sol b/contracts/mocks/MulticallMock.sol index e9aa84a8..36ca1bf3 100644 --- a/contracts/mocks/MulticallMock.sol +++ b/contracts/mocks/MulticallMock.sol @@ -7,14 +7,9 @@ pragma abicoder v2; import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import "../interfaces/IOracles.sol"; import "../interfaces/IMerkleDistributor.sol"; +import "../interfaces/IRewardEthToken.sol"; contract MulticallMock { - struct OracleRewards { - uint256 totalRewards; - uint256 activatedValidators; - bytes[] signatures; - } - struct MerkleRoot { bytes32 merkleRoot; string merkleProofs; @@ -23,42 +18,47 @@ contract MulticallMock { IOracles private oracles; IERC20Upgradeable private stakedEthToken; - IERC20Upgradeable private rewardEthToken; + IRewardEthToken private rewardEthToken; IMerkleDistributor private merkleDistributor; constructor(address _oracles, address _stakedEthToken, address _rewardEthToken, address _merkleDistributor) { oracles = IOracles(_oracles); stakedEthToken = IERC20Upgradeable(_stakedEthToken); - rewardEthToken = IERC20Upgradeable(_rewardEthToken); + rewardEthToken = IRewardEthToken(_rewardEthToken); merkleDistributor = IMerkleDistributor(_merkleDistributor); } function transferRewardsAndUpdateTotalRewards( - uint256 totalRewards, - uint256 activatedValidators, - address payee, - bytes[] calldata signatures + int256 rewardsDelta, + address payee ) external { rewardEthToken.transferFrom(msg.sender, payee, rewardEthToken.balanceOf(msg.sender)); - oracles.submitRewards(totalRewards, activatedValidators, signatures); + rewardEthToken.updateTotalRewards(rewardsDelta); } function updateTotalRewardsAndTransferRewards( - uint256 totalRewards, - uint256 activatedValidators, - address payee, - bytes[] calldata signatures + int256 rewardsDelta, + address payee ) external { - oracles.submitRewards(totalRewards, activatedValidators, signatures); + rewardEthToken.updateTotalRewards(rewardsDelta); rewardEthToken.transferFrom(msg.sender, payee, rewardEthToken.balanceOf(msg.sender)); } + function updateTotalRewardsAndMigrate(int256 rewardsDelta) external { + rewardEthToken.updateTotalRewards(rewardsDelta); + rewardEthToken.migrate( + msg.sender, + stakedEthToken.balanceOf(address(this)), + rewardEthToken.balanceOf(address(this)) + ); + } + function updateTotalRewardsAndClaim( - OracleRewards memory oracleRewards, + int256 rewardsDelta, uint256 index, address account, address[] calldata tokens, @@ -67,12 +67,12 @@ contract MulticallMock { ) external { - oracles.submitRewards(oracleRewards.totalRewards, oracleRewards.activatedValidators, oracleRewards.signatures); + rewardEthToken.updateTotalRewards(rewardsDelta); merkleDistributor.claim(index, account, tokens, amounts, merkleProof); } function claimAndUpdateTotalRewards( - OracleRewards memory oracleRewards, + int256 rewardsDelta, uint256 index, address account, address[] calldata tokens, @@ -82,7 +82,7 @@ contract MulticallMock { external { merkleDistributor.claim(index, account, tokens, amounts, merkleProof); - oracles.submitRewards(oracleRewards.totalRewards, oracleRewards.activatedValidators, oracleRewards.signatures); + rewardEthToken.updateTotalRewards(rewardsDelta); } function claimAndUpdateMerkleRoot( @@ -114,36 +114,32 @@ contract MulticallMock { } function updateTotalRewardsAndTransferStakedEth( - uint256 totalRewards, - uint256 activatedValidators, - address payee, - bytes[] calldata signatures + int256 rewardsDelta, + address payee ) external { - oracles.submitRewards(totalRewards, activatedValidators, signatures); + rewardEthToken.updateTotalRewards(rewardsDelta); stakedEthToken.transferFrom(msg.sender, payee, stakedEthToken.balanceOf(msg.sender)); } function transferStakedEthAndUpdateTotalRewards( - uint256 totalRewards, - uint256 activatedValidators, - address payee, - bytes[] calldata signatures + int256 rewardsDelta, + address payee ) external { stakedEthToken.transferFrom(msg.sender, payee, stakedEthToken.balanceOf(msg.sender)); - oracles.submitRewards(totalRewards, activatedValidators, signatures); + rewardEthToken.updateTotalRewards(rewardsDelta); } function updateTotalRewardsAndMerkleRoot( - OracleRewards memory oracleRewards, + int256 rewardsDelta, MerkleRoot memory merkleRoot ) external { - oracles.submitRewards(oracleRewards.totalRewards, oracleRewards.activatedValidators, oracleRewards.signatures); + rewardEthToken.updateTotalRewards(rewardsDelta); oracles.submitMerkleRoot(merkleRoot.merkleRoot, merkleRoot.merkleProofs, merkleRoot.signatures); } } diff --git a/contracts/mocks/VaultMock.sol b/contracts/mocks/VaultMock.sol new file mode 100644 index 00000000..c96e05e2 --- /dev/null +++ b/contracts/mocks/VaultMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +pragma solidity 0.7.5; + +import {IRewardEthToken} from "../interfaces/IRewardEthToken.sol"; + + +contract VaultMock { + event Migrated(address receiver, uint256 assets, uint256 shares); + + IRewardEthToken private rewardEthToken; + uint256 public migratedAssets; + + constructor(address _rewardEthToken) { + rewardEthToken = IRewardEthToken(_rewardEthToken); + } + + function updateTotalRewards(int256 rewardsDelta) external { + rewardEthToken.updateTotalRewards(rewardsDelta); + } + + function migrate(address receiver, uint256 assets) external returns (uint256 shares) { + shares = assets; + migratedAssets = assets; + emit Migrated(receiver, assets, shares); + } +} diff --git a/contracts/pool/Pool.sol b/contracts/pool/Pool.sol index 02edb15e..0ab85663 100644 --- a/contracts/pool/Pool.sol +++ b/contracts/pool/Pool.sol @@ -3,11 +3,9 @@ pragma solidity 0.7.5; pragma abicoder v2; -import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import "../presets/OwnablePausableUpgradeable.sol"; import "../interfaces/IStakedEthToken.sol"; import "../interfaces/IDepositContract.sol"; -import "../interfaces/IPoolValidators.sol"; import "../interfaces/IPool.sol"; import "../interfaces/IPoolValidators.sol"; @@ -17,19 +15,17 @@ import "../interfaces/IPoolValidators.sol"; * @dev Pool contract accumulates deposits from the users, mints tokens and registers validators. */ contract Pool is IPool, OwnablePausableUpgradeable { - using SafeMathUpgradeable for uint256; - - // @dev Validator deposit amount. - uint256 public constant override VALIDATOR_TOTAL_DEPOSIT = 32 ether; + // @dev Address of the PoolEscrow contract. + address public immutable override poolEscrow; // @dev Total activated validators. - uint256 public override activatedValidators; + uint256 private activatedValidators; // @dev Pool validator withdrawal credentials. - bytes32 public override withdrawalCredentials; + bytes32 private withdrawalCredentials; // @dev Address of the ETH2 Deposit Contract (deployed by Ethereum). - IDepositContract public override validatorRegistration; + IDepositContract private validatorRegistration; // @dev Address of the StakedEthToken contract. IStakedEthToken private stakedEthToken; @@ -41,197 +37,39 @@ contract Pool is IPool, OwnablePausableUpgradeable { address private oracles; // @dev Maps senders to the validator index that it will be activated in. - mapping(address => mapping(uint256 => uint256)) public override activations; + mapping(address => mapping(uint256 => uint256)) private activations; // @dev Total pending validators. - uint256 public override pendingValidators; + uint256 private pendingValidators; // @dev Amount of deposited ETH that is not considered for the activation period. - uint256 public override minActivatingDeposit; + uint256 private minActivatingDeposit; // @dev Pending validators percent limit. If it's not exceeded tokens can be minted immediately. - uint256 public override pendingValidatorsLimit; + uint256 private pendingValidatorsLimit; - /** - * @dev See {IPool-setMinActivatingDeposit}. - */ - function setMinActivatingDeposit(uint256 newMinActivatingDeposit) external override onlyAdmin { - minActivatingDeposit = newMinActivatingDeposit; - emit MinActivatingDepositUpdated(newMinActivatingDeposit, msg.sender); + /** + * @dev Constructor + * @dev Since the immutable variable value is stored in the bytecode, + * its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + * @param _poolEscrow Address of the PoolEscrow contract. + */ + constructor(address _poolEscrow) { + poolEscrow = _poolEscrow; } /** - * @dev See {IPool-setPendingValidatorsLimit}. + * @dev See {IPool-transferToPoolEscrow}. */ - function setPendingValidatorsLimit(uint256 newPendingValidatorsLimit) external override onlyAdmin { - require(newPendingValidatorsLimit < 1e4, "Pool: invalid limit"); - pendingValidatorsLimit = newPendingValidatorsLimit; - emit PendingValidatorsLimitUpdated(newPendingValidatorsLimit, msg.sender); - } - - /** - * @dev See {IPool-setActivatedValidators}. - */ - function setActivatedValidators(uint256 newActivatedValidators) external override { - require(msg.sender == oracles || hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Pool: access denied"); - - // subtract activated validators from pending validators - pendingValidators = pendingValidators.sub(newActivatedValidators.sub(activatedValidators)); - activatedValidators = newActivatedValidators; - emit ActivatedValidatorsUpdated(newActivatedValidators, msg.sender); - } - - /** - * @dev See {IPool-stake}. - */ - function stake() external payable override { - _stake(msg.sender, msg.value); - } - - /** - * @dev See {IPool-stakeOnBehalf}. - */ - function stakeOnBehalf(address recipient) external payable override { - _stake(recipient, msg.value); + function transferToPoolEscrow() external override { + uint256 balance = address(this).balance; + // solhint-disable-next-line avoid-low-level-calls + (bool success,) = payable(poolEscrow).call{value: balance}(""); + require(success, "Pool: transfer failed"); } /** * @dev See {IPool-receiveFees}. */ function receiveFees() external payable override {} - - /** - * @dev Function for staking ETH using transfer. - */ - receive() external payable { - _stake(msg.sender, msg.value); - } - - /** - * @dev See {IPool-stakeWithPartner}. - */ - function stakeWithPartner(address partner) external payable override { - // stake amount - _stake(msg.sender, msg.value); - emit StakedWithPartner(partner, msg.value); - } - - /** - * @dev See {IPool-stakeWithPartnerOnBehalf}. - */ - function stakeWithPartnerOnBehalf(address partner, address recipient) external payable override { - // stake amount - _stake(recipient, msg.value); - emit StakedWithPartner(partner, msg.value); - } - - /** - * @dev See {IPool-stakeWithReferrer}. - */ - function stakeWithReferrer(address referrer) external payable override { - // stake amount - _stake(msg.sender, msg.value); - emit StakedWithReferrer(referrer, msg.value); - } - - /** - * @dev See {IPool-stakeWithReferrerOnBehalf}. - */ - function stakeWithReferrerOnBehalf(address referrer, address recipient) external payable override { - // stake amount - _stake(recipient, msg.value); - emit StakedWithReferrer(referrer, msg.value); - } - - function _stake(address recipient, uint256 value) internal whenNotPaused { - require(recipient != address(0), "Pool: invalid recipient"); - require(value > 0, "Pool: invalid deposit amount"); - - // mint tokens for small deposits immediately - if (value <= minActivatingDeposit) { - stakedEthToken.mint(recipient, value); - return; - } - - // mint tokens if current pending validators limit is not exceed - uint256 _pendingValidators = pendingValidators.add((address(this).balance).div(VALIDATOR_TOTAL_DEPOSIT)); - uint256 _activatedValidators = activatedValidators; // gas savings - uint256 validatorIndex = _activatedValidators.add(_pendingValidators); - if (validatorIndex.mul(1e4) <= _activatedValidators.mul(pendingValidatorsLimit.add(1e4))) { - stakedEthToken.mint(recipient, value); - } else { - // lock deposit amount until validator activated - activations[recipient][validatorIndex] = activations[recipient][validatorIndex].add(value); - emit ActivationScheduled(recipient, validatorIndex, value); - } - } - - /** - * @dev See {IPool-canActivate}. - */ - function canActivate(uint256 validatorIndex) external view override returns (bool) { - return validatorIndex.mul(1e4) <= activatedValidators.mul(pendingValidatorsLimit.add(1e4)); - } - - /** - * @dev See {IPool-activate}. - */ - function activate(address account, uint256 validatorIndex) external override whenNotPaused { - uint256 activatedAmount = _activateAmount( - account, - validatorIndex, - activatedValidators.mul(pendingValidatorsLimit.add(1e4)) - ); - - stakedEthToken.mint(account, activatedAmount); - } - - /** - * @dev See {IPool-activateMultiple}. - */ - function activateMultiple(address account, uint256[] calldata validatorIndexes) external override whenNotPaused { - uint256 toMint; - uint256 maxValidatorIndex = activatedValidators.mul(pendingValidatorsLimit.add(1e4)); - for (uint256 i = 0; i < validatorIndexes.length; i++) { - uint256 activatedAmount = _activateAmount(account, validatorIndexes[i], maxValidatorIndex); - toMint = toMint.add(activatedAmount); - } - stakedEthToken.mint(account, toMint); - } - - function _activateAmount( - address account, - uint256 validatorIndex, - uint256 maxValidatorIndex - ) - internal returns (uint256 amount) - { - require(validatorIndex.mul(1e4) <= maxValidatorIndex, "Pool: validator is not active yet"); - - amount = activations[account][validatorIndex]; - require(amount > 0, "Pool: invalid validator index"); - - delete activations[account][validatorIndex]; - emit Activated(account, validatorIndex, amount, msg.sender); - } - - /** - * @dev See {IPool-registerValidator}. - */ - function registerValidator(IPoolValidators.DepositData calldata depositData) external override whenNotPaused { - require(msg.sender == address(validators), "Pool: access denied"); - require(depositData.withdrawalCredentials == withdrawalCredentials, "Pool: invalid withdrawal credentials"); - - // update number of pending validators - pendingValidators = pendingValidators.add(1); - emit ValidatorRegistered(depositData.publicKey, depositData.operator); - - // register validator - validatorRegistration.deposit{value : VALIDATOR_TOTAL_DEPOSIT}( - depositData.publicKey, - abi.encodePacked(depositData.withdrawalCredentials), - depositData.signature, - depositData.depositDataRoot - ); - } } diff --git a/contracts/pool/PoolValidators.sol b/contracts/pool/PoolValidators.sol deleted file mode 100644 index 5ba4dd5b..00000000 --- a/contracts/pool/PoolValidators.sol +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only - -pragma solidity 0.7.5; -pragma abicoder v2; - -import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/cryptography/MerkleProofUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; -import "../presets/OwnablePausableUpgradeable.sol"; -import "../interfaces/IPoolValidators.sol"; -import "../interfaces/IPool.sol"; - -/** - * @title PoolValidators - * - * @dev PoolValidators contract keeps track of the pool validators' deposit data and onboards new operators. - */ -contract PoolValidators is IPoolValidators, OwnablePausableUpgradeable, ReentrancyGuardUpgradeable { - using AddressUpgradeable for address payable; - using SafeMathUpgradeable for uint256; - - // Maps hash of the validator public key to whether it is registered. - mapping(bytes32 => bool) public override isValidatorRegistered; - - // Maps operator address to its data. - mapping(address => Operator) private operators; - - // @dev Address of the Pool contract. - IPool private pool; - - // @dev Address of the Oracles contract. - address private oracles; - - /** - * @dev See {IPoolValidators-initialize}. - */ - function initialize(address _admin, address _pool, address _oracles) external override initializer { - require(_admin != address(0), "Pool: invalid admin address"); - require(_pool != address(0), "Pool: invalid Pool address"); - require(_oracles != address(0), "Pool: invalid Oracles address"); - - __OwnablePausableUpgradeable_init(_admin); - pool = IPool(_pool); - oracles = _oracles; - } - - /** - * @dev See {IPoolValidators-getOperator}. - */ - function getOperator(address _operator) external view override returns (bytes32, bool) { - Operator storage operator = operators[_operator]; - return ( - operator.depositDataMerkleRoot, - operator.committed - ); - } - - /** - * @dev See {IPoolValidators-addOperator}. - */ - function addOperator( - address _operator, - bytes32 depositDataMerkleRoot, - string calldata depositDataMerkleProofs - ) - external override onlyAdmin whenNotPaused - { - require(_operator != address(0), "PoolValidators: invalid operator"); - // merkle root and proofs must be validated off chain prior submitting the transaction - require(depositDataMerkleRoot != "", "PoolValidators: invalid merkle root"); - require(bytes(depositDataMerkleProofs).length != 0, "PoolValidators: invalid merkle proofs"); - - // load operator - Operator storage operator = operators[_operator]; - require(operator.depositDataMerkleRoot != depositDataMerkleRoot, "PoolValidators: same merkle root"); - - // update operator - operator.depositDataMerkleRoot = depositDataMerkleRoot; - operator.committed = false; - - emit OperatorAdded( - _operator, - depositDataMerkleRoot, - depositDataMerkleProofs - ); - } - - /** - * @dev See {IPoolValidators-commitOperator}. - */ - function commitOperator() external override whenNotPaused { - // mark operator as committed - Operator storage operator = operators[msg.sender]; - require(operator.depositDataMerkleRoot != "" && !operator.committed, "PoolValidators: invalid operator"); - operator.committed = true; - - emit OperatorCommitted(msg.sender); - } - - /** - * @dev See {IPoolValidators-removeOperator}. - */ - function removeOperator(address _operator) external override whenNotPaused { - require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || msg.sender == _operator, "PoolValidators: access denied"); - - Operator storage operator = operators[_operator]; - require(operator.depositDataMerkleRoot != "", "PoolValidators: invalid operator"); - - // clean up operator - delete operators[_operator]; - - emit OperatorRemoved(msg.sender, _operator); - } - - /** - * @dev See {IPoolValidators-registerValidator}. - */ - function registerValidator(DepositData calldata depositData, bytes32[] calldata merkleProof) external override { - require(msg.sender == oracles, "PoolValidators: access denied"); - - // mark validator as registered -> prevents from registering the same validator twice - bytes32 validatorId = keccak256(abi.encode(depositData.publicKey)); - require(!isValidatorRegistered[validatorId], "PoolValidators: validator already registered"); - isValidatorRegistered[validatorId] = true; - - // fetch deposit data merkle root - Operator storage operator = operators[depositData.operator]; - bytes32 depositDataMerkleRoot = operator.depositDataMerkleRoot; - require(depositDataMerkleRoot != "" && operator.committed, "PoolValidators: invalid operator"); - - // check whether provided deposit data was previously approved - bytes32 node = keccak256(abi.encode( - depositData.publicKey, - depositData.withdrawalCredentials, - depositData.signature, - depositData.depositDataRoot - )); - require( - MerkleProofUpgradeable.verify(merkleProof, depositDataMerkleRoot, node), - "PoolValidators: invalid merkle proof" - ); - - // register validator - pool.registerValidator(depositData); - } -} diff --git a/contracts/tokens/RewardEthToken.sol b/contracts/tokens/RewardEthToken.sol index e79e02b8..9266dbbb 100644 --- a/contracts/tokens/RewardEthToken.sol +++ b/contracts/tokens/RewardEthToken.sol @@ -8,8 +8,9 @@ import "../presets/OwnablePausableUpgradeable.sol"; import "../interfaces/IStakedEthToken.sol"; import "../interfaces/IRewardEthToken.sol"; import "../interfaces/IMerkleDistributor.sol"; -import "../interfaces/IOracles.sol"; import "../interfaces/IFeesEscrow.sol"; +import "../interfaces/IEthGenesisVault.sol"; +import "../interfaces/IPool.sol"; import "./ERC20PermitUpgradeable.sol"; /** @@ -22,6 +23,12 @@ contract RewardEthToken is IRewardEthToken, OwnablePausableUpgradeable, ERC20Per using SafeMathUpgradeable for uint256; using SafeCastUpgradeable for uint256; + // @dev Address of the Vault contract. + address public immutable override vault; + + // @dev Address of the Pool contract. + IPool private immutable pool; + // @dev Address of the StakedEthToken contract. IStakedEthToken private stakedEthToken; @@ -55,6 +62,21 @@ contract RewardEthToken is IRewardEthToken, OwnablePausableUpgradeable, ERC20Per // @dev Address of the FeesEscrow contract. IFeesEscrow private feesEscrow; + // @dev Total amount of penalties received. + uint256 public override totalPenalty; + + /** + * @dev Constructor + * @dev Since the immutable variable value is stored in the bytecode, + * its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + * @param _vault Address of the StakeWise V3 vault. + * @param _pool Address of the StakeWise V2 pool. + */ + constructor(address _vault, address _pool) { + vault = _vault; + pool = IPool(_pool); + } + /** * @dev See {IRewardEthToken-setRewardsDisabled}. */ @@ -97,6 +119,13 @@ contract RewardEthToken is IRewardEthToken, OwnablePausableUpgradeable, ERC20Per return totalRewards; } + /** + * @dev See {IRewardEthToken-totalAssets}. + */ + function totalAssets() public view override returns (uint256) { + return uint256(totalRewards).add(stakedEthToken.totalSupply()); + } + /** * @dev See {IERC20-balanceOf}. */ @@ -203,18 +232,35 @@ contract RewardEthToken is IRewardEthToken, OwnablePausableUpgradeable, ERC20Per /** * @dev See {IRewardEthToken-updateTotalRewards}. */ - function updateTotalRewards(uint256 newTotalRewards) external override { - require(msg.sender == oracles, "RewardEthToken: access denied"); + function updateTotalRewards(int256 rewardsDelta) external override { + require(msg.sender == address(vault), "RewardEthToken: access denied"); + + uint256 periodRewards; + if (rewardsDelta > 0) { + periodRewards = uint256(rewardsDelta); + uint256 _totalPenalty = totalPenalty; // gas savings + if (periodRewards <= _totalPenalty) { + totalPenalty = _totalPenalty.sub(periodRewards); + periodRewards = 0; + } else if (_totalPenalty > 0) { + periodRewards = periodRewards.sub(_totalPenalty); + totalPenalty = 0; + } + } else if (rewardsDelta < 0) { + uint256 _totalPenalty = totalPenalty; // gas savings + _totalPenalty = _totalPenalty.add(uint256(-rewardsDelta)); + require(_totalPenalty <= totalAssets(), "RewardEthToken: invalid penalty amount"); + totalPenalty = _totalPenalty; + } - newTotalRewards = newTotalRewards.add(feesEscrow.transferToPool()); - uint256 periodRewards = newTotalRewards.sub(totalRewards); if (periodRewards == 0) { lastUpdateBlockNumber = block.number; - emit RewardsUpdated(0, newTotalRewards, rewardPerToken, 0, 0); + emit RewardsUpdated(0, totalRewards, rewardPerToken, 0, 0); return; } // calculate protocol reward and new reward per token amount + uint256 newTotalRewards = uint256(totalRewards).add(periodRewards); uint256 protocolReward = periodRewards.mul(protocolFee).div(1e4); uint256 prevRewardPerToken = rewardPerToken; uint256 newRewardPerToken = prevRewardPerToken.add(periodRewards.sub(protocolReward).mul(1e18).div(stakedEthToken.totalDeposits())); @@ -247,6 +293,12 @@ contract RewardEthToken is IRewardEthToken, OwnablePausableUpgradeable, ERC20Per }); } + // transfer accumulated fees + if (address(feesEscrow).balance > 0) { + feesEscrow.transferToPool(); + pool.transferToPoolEscrow(); + } + lastUpdateBlockNumber = block.number; emit RewardsUpdated( periodRewards, @@ -257,6 +309,43 @@ contract RewardEthToken is IRewardEthToken, OwnablePausableUpgradeable, ERC20Per ); } + function _burn(uint256 amount) private { + uint128 _rewardPerToken = rewardPerToken; // gas savings + checkpoints[msg.sender] = Checkpoint({ + reward: _balanceOf(msg.sender, _rewardPerToken).sub(amount).toUint128(), + rewardPerToken: _rewardPerToken + }); + totalRewards = uint256(totalRewards).sub(amount).toUint128(); + emit Transfer(msg.sender, address(0), amount); + } + + /** + * @dev See {IRewardEthToken-migrate}. + */ + function migrate(address receiver, uint256 principal, uint256 reward) external override { + require(receiver != address(0), "RewardEthToken: invalid receiver"); + require(block.number > lastUpdateBlockNumber, "RewardEthToken: cannot migrate during rewards update"); + + // calculate amount of assets to migrate + uint256 assets = principal.add(reward); + + uint256 _totalPenalty = totalPenalty; // gas savings + if (_totalPenalty > 0) { + uint256 _totalAssets = totalAssets(); // gas savings + // apply penalty to assets + uint256 assetsAfterPenalty = assets.mul(_totalAssets.sub(_totalPenalty)).div(_totalAssets); + totalPenalty = _totalPenalty.add(assetsAfterPenalty).sub(assets); + assets = assetsAfterPenalty; + } + require(assets > 0, "RewardEthToken: zero assets"); + + // burn rewards and principal + if (reward > 0) _burn(reward); + if (principal > 0) stakedEthToken.burn(msg.sender, principal); + + IEthGenesisVault(vault).migrate(receiver, assets); + } + /** * @dev See {IRewardEthToken-claim}. */ diff --git a/contracts/tokens/StakedEthToken.sol b/contracts/tokens/StakedEthToken.sol index d79c5416..9a988d86 100644 --- a/contracts/tokens/StakedEthToken.sol +++ b/contracts/tokens/StakedEthToken.sol @@ -91,21 +91,23 @@ contract StakedEthToken is IStakedEthToken, OwnablePausableUpgradeable, ERC20Per } /** - * @dev See {IStakedEthToken-mint}. + * @dev See {IStakedEthToken-burn}. */ - function mint(address account, uint256 amount) external override { - require(msg.sender == pool, "StakedEthToken: access denied"); + function burn(address account, uint256 amount) external override { + IRewardEthToken _rewardEthToken = rewardEthToken; // gas savings + require(msg.sender == address(_rewardEthToken), "StakedEthToken: access denied"); + require(account != address(0), "StakedEthToken: invalid account"); // start calculating account rewards with updated deposit amount - bool rewardsDisabled = rewardEthToken.updateRewardCheckpoint(account); + bool rewardsDisabled = _rewardEthToken.updateRewardCheckpoint(account); if (rewardsDisabled) { // update merkle distributor principal if account has disabled rewards - distributorPrincipal = distributorPrincipal.add(amount); + distributorPrincipal = distributorPrincipal.sub(amount); } - totalDeposits = totalDeposits.add(amount); - deposits[account] = deposits[account].add(amount); + totalDeposits = totalDeposits.sub(amount); + deposits[account] = deposits[account].sub(amount); - emit Transfer(address(0), account, amount); + emit Transfer(account, address(0), amount); } } diff --git a/deployments/index.js b/deployments/index.js index 83339ee5..e290a279 100644 --- a/deployments/index.js +++ b/deployments/index.js @@ -1,14 +1,89 @@ -const { contracts } = require('./settings'); +const { white } = require('chalk'); +const { ethers, upgrades, config } = require('hardhat'); +const { contracts, contractSettings } = require('./settings'); +const { + silenceWarnings, +} = require('@openzeppelin/upgrades-core/dist/utils/log'); + +function log(message) { + if (config != null && config.suppressLogs !== true) { + console.log(message); + } +} + +async function upgradePool(signer) { + const Pool = await ethers.getContractFactory('Pool', signer); + + // upgrade Pool to new implementation + const proxy = await upgrades.upgradeProxy(contracts.pool, Pool, { + unsafeAllow: ['state-variable-immutable', 'constructor'], + constructorArgs: [contracts.poolEscrow], + }); + await proxy.deployed(); +} + +async function upgradeOracles(signer) { + const Oracles = await ethers.getContractFactory('Oracles', signer); + + // upgrade Pool to new implementation + const proxy = await upgrades.upgradeProxy(contracts.oracles, Oracles); + await proxy.deployed(); +} + +async function upgradeRewardEthToken(signer, vault) { + const RewardEthToken = await ethers.getContractFactory( + 'RewardEthToken', + signer + ); + // upgrade RewardEthToken to new implementation + const proxy = await upgrades.upgradeProxy( + contracts.rewardEthToken, + RewardEthToken, + { + unsafeAllow: ['state-variable-immutable', 'constructor'], + constructorArgs: [vault, contracts.pool], + } + ); + await proxy.deployed(); +} + +async function upgradeStakedEthToken(signer) { + const StakedEthToken = await ethers.getContractFactory( + 'StakedEthToken', + signer + ); + // upgrade RewardEthToken to new implementation + const proxy = await upgrades.upgradeProxy( + contracts.stakedEthToken, + StakedEthToken + ); + await proxy.deployed(); +} async function deployContracts() { return contracts; } -async function upgradeContracts() { +async function upgradeContracts(vault = contracts.vault) { + const signer = await ethers.provider.getSigner(contractSettings.admin); + silenceWarnings(); + await upgradeOracles(signer); + log(white('Upgraded Oracles contract')); + + await upgradePool(signer); + log(white('Upgraded Pool contract')); + + await upgradeRewardEthToken(signer, vault); + log(white('Upgraded RewardEthToken contract')); + + await upgradeStakedEthToken(signer); + log(white('Upgraded StakedEthToken contract')); + return contracts; } module.exports = { deployContracts, upgradeContracts, + upgradeRewardEthToken, }; diff --git a/deployments/settings.js b/deployments/settings.js index cca6bb68..998b0a16 100644 --- a/deployments/settings.js +++ b/deployments/settings.js @@ -17,10 +17,11 @@ if (hre.hardhatArguments && hre.hardhatArguments.network === 'goerli') { feesEscrow: '0x6A9d30e05C6832E868390F155388c7d97A6faEAC', contractChecker: '0x85ee326f839Bc430655A3fad447837072ef52C2F', proxyAdmin: '0xbba3f4dDD4F705aD2028ee2da64fF3166bDe8cA8', + vault: '0x8B5c0d4c067Cf480766140B251B464de7F03B515', }; contractSettings = { - admin: '0x1867c96601bc5fE24F685d112314B8F3Fe228D5A', + admin: '0xff2b6d2d5c205b99e2e6f607b6afa3127b9957b6', vestingEscrow: '0x4CDAe3f1Eaa84b88fFc97627Ef1c77F762794287', }; } else { @@ -39,6 +40,7 @@ if (hre.hardhatArguments && hre.hardhatArguments.network === 'goerli') { feesEscrow: '0x6b333B20fBae3c5c0969dd02176e30802e2fbBdB', contractChecker: '0xFC1fC7257AEA7C7c08A498594DCA97CE5A72fdCB', proxyAdmin: '0x3EB0175dcD67d3AB139aA03165e24AA2188A4C22', + vault: '0xAC0F906E433d58FA868F936E8A43230473652885', }; contractSettings = { diff --git a/hardhat.config.js b/hardhat.config.js index 5ffacbd1..ea2eb0d5 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -10,7 +10,7 @@ require('hardhat-contract-sizer'); require('hardhat-abi-exporter'); require('@nomiclabs/hardhat-etherscan'); -const BLOCK_NUMBER = 15545080; +const BLOCK_NUMBER = 17769320; const OPTIMIZER_RUNS = 5000000; const log = (...text) => console.log(gray(...['└─> [DEBUG]'].concat(text))); @@ -140,6 +140,7 @@ module.exports = { 'MerkleDistributor', 'ContractChecker', 'Roles', + 'FeesEscrow', ], clear: true, flat: true, diff --git a/networks/goerli.md b/networks/goerli.md index e4a62b35..80e7dcf1 100755 --- a/networks/goerli.md +++ b/networks/goerli.md @@ -19,6 +19,12 @@ - Implementation: [0x63343E28878aA031044Fa81C10BF493119536746](https://goerli.etherscan.io/address/0x63343E28878aA031044Fa81C10BF493119536746) - Transaction: [0x43e26b4a71fc1140f896327f5f3b0324602823dbe126e70b9401d1f3293acea2](https://goerli.etherscan.io/tx/0x43e26b4a71fc1140f896327f5f3b0324602823dbe126e70b9401d1f3293acea2) +- + +### Upgrade to v3.0.0 + +- Implementation: [0x930b1DF55f1775eee2f7d527e61eBDE7d421F1Ee](https://goerli.etherscan.io/address/0x930b1df55f1775eee2f7d527e61ebde7d421f1ee) +- Transaction: [0xe089365b5b49ff99a3530fa1e96f247ff741f0eaa82665fa52764622af3b0e25](https://goerli.etherscan.io/tx/0xe089365b5b49ff99a3530fa1e96f247ff741f0eaa82665fa52764622af3b0e25) ## Pool Escrow @@ -40,6 +46,11 @@ - Contract: [0x221D9812823DBAb0F1fB40b0D294D9875980Ac19](https://goerli.etherscan.io/address/0x221D9812823DBAb0F1fB40b0D294D9875980Ac19) - Transaction: [0xd745ea1ff1ada4725d40b2c7e73ba4bbc7171ab513822a0838e21a55d1c3a44c](https://goerli.etherscan.io/tx/0xd745ea1ff1ada4725d40b2c7e73ba4bbc7171ab513822a0838e21a55d1c3a44c) +### Upgrade to v3.0.0 + +- Implementation: [0x37a53715c8229bf0d811f03a1f0ec74927a87252](https://goerli.etherscan.io/address/0x37a53715c8229bf0d811f03a1f0ec74927a87252) +- Transaction: [0xe089365b5b49ff99a3530fa1e96f247ff741f0eaa82665fa52764622af3b0e25](https://goerli.etherscan.io/tx/0xe089365b5b49ff99a3530fa1e96f247ff741f0eaa82665fa52764622af3b0e25) + ## RewardEthToken - Contract: [0x826f88d423440c305D9096cC1581Ae751eFCAfB0](https://goerli.etherscan.io/address/0x826f88d423440c305D9096cC1581Ae751eFCAfB0) @@ -55,6 +66,16 @@ - Implementation: [0x895175E34FAB2602979A8374CFC757c7F409eb88](https://goerli.etherscan.io/address/0x895175E34FAB2602979A8374CFC757c7F409eb88) - Transaction: [0x932172d7272a20a0369b6210d97c856bdc5633fb80cf94a00f673a0a5f526072](https://goerli.etherscan.io/tx/0x932172d7272a20a0369b6210d97c856bdc5633fb80cf94a00f673a0a5f526072) +### Upgrade to v3.0.0 + +- Implementation: [0xDD16012a25f64a15D6C6F2C32994dE47AB9d2905](https://goerli.etherscan.io/address/0xdd16012a25f64a15d6c6f2c32994de47ab9d2905) +- Transaction: [0x77d5181166692ebbf609e2004bcd68c987d75f5f31becb87dc97549d6985dfc3](https://goerli.etherscan.io/tx/0x77d5181166692ebbf609e2004bcd68c987d75f5f31becb87dc97549d6985dfc3) + +### Upgrade to v3.0.1 + +- Implementation: [0x46a17BA609f2D0883AbdAA861aDD44A3Dd8AFB48](https://goerli.etherscan.io/address/0x46a17ba609f2d0883abdaa861add44a3dd8afb48#code) +- Transaction: [0x56ae16e4da42bb722f4b783873812a68745e6a3cea5f1a0b52d18ba70c3d646b](https://goerli.etherscan.io/tx/0x56ae16e4da42bb722f4b783873812a68745e6a3cea5f1a0b52d18ba70c3d646b) + ## StakeWiseToken - Contract: [0x0e2497aACec2755d831E4AFDEA25B4ef1B823855](https://goerli.etherscan.io/address/0x0e2497aACec2755d831E4AFDEA25B4ef1B823855) @@ -65,6 +86,11 @@ - Contract: [0x531b9D9cb268E88D53A87890699bbe31326A6f08](https://goerli.etherscan.io/address/0x531b9D9cb268E88D53A87890699bbe31326A6f08) - Transaction: [0x37dd82caa30c517030bd3611b27f41207ff71d526cbeb452ed771edd01e5208d](https://goerli.etherscan.io/tx/0x37dd82caa30c517030bd3611b27f41207ff71d526cbeb452ed771edd01e5208d) +### Upgrade to v3.0.0 + +- Implementation: [0xd234E23bB8e12f064e4E51E1B9F5F492da718deA](https://goerli.etherscan.io/address/0xd234e23bb8e12f064e4e51e1b9f5f492da718dea) +- Transaction: [0x44200206ad423de62baee98bfa033bb775926bc897fe36cf4d1692589d39009a](https://goerli.etherscan.io/tx/0x44200206ad423de62baee98bfa033bb775926bc897fe36cf4d1692589d39009a) + ## Vesting Escrow - Contract: [0xBe8D03Dcf686C7C6238F5051bB136f503b05F1D7](https://goerli.etherscan.io/address/0xBe8D03Dcf686C7C6238F5051bB136f503b05F1D7) diff --git a/networks/mainnet.md b/networks/mainnet.md index 2e1918ea..b51b778b 100644 --- a/networks/mainnet.md +++ b/networks/mainnet.md @@ -20,6 +20,11 @@ - Implementation: [0x61975c09207c5DFe794b0A652C8CAf8458159AAe](https://etherscan.io/address/0x61975c09207c5DFe794b0A652C8CAf8458159AAe) - Transaction: [0x0bfabc6866c599c84459f37d08f49d032aac2293c269be6bbb9b93d2e01ad0fb](https://etherscan.io/tx/0x0bfabc6866c599c84459f37d08f49d032aac2293c269be6bbb9b93d2e01ad0fb) +### Upgrade to v3.0.0 + +- Implementation: [0x481f28C0D733614aF87897E43d0D52C451799592](https://etherscan.io/address/0x481f28C0D733614aF87897E43d0D52C451799592) +- Transaction: [0x44708e829eff49cc466ae6bd1b261a15ba973b6043ad850386177e61a1626fd5](https://etherscan.io/tx/0x44708e829eff49cc466ae6bd1b261a15ba973b6043ad850386177e61a1626fd5) + ## Pool Escrow - Contract: [0x2296e122c1a20Fca3CAc3371357BdAd3be0dF079](https://etherscan.io/address/0x2296e122c1a20Fca3CAc3371357BdAd3be0dF079) @@ -45,6 +50,11 @@ - Implementation: [0x41bcac23e4db058d8D7aAbE2Fccdae5F01FE647A](https://etherscan.io/address/0x41bcac23e4db058d8D7aAbE2Fccdae5F01FE647A) - Transaction: [0x71b7c78f4526efbef1e31d2af8ee1f942e8cceaa384cd0cf2ad2c008fe14a9c3](https://etherscan.io/tx/0x71b7c78f4526efbef1e31d2af8ee1f942e8cceaa384cd0cf2ad2c008fe14a9c3) +### Upgrade to v3.0.0 + +- Implementation: [0x82FE8C78CaE0013471179e76224ef89941bAaa75](https://etherscan.io/address/0x82FE8C78CaE0013471179e76224ef89941bAaa75) +- Transaction: [0xbb4e9b9cf7909a4fc360b27273855c1dff12f35f76fc3cf7d5ad1ee7e7bda262](https://etherscan.io/tx/0xbb4e9b9cf7909a4fc360b27273855c1dff12f35f76fc3cf7d5ad1ee7e7bda262) + ## RewardEthToken - Contract: [0x20BC832ca081b91433ff6c17f85701B6e92486c5](https://etherscan.io/address/0x20BC832ca081b91433ff6c17f85701B6e92486c5) @@ -70,6 +80,11 @@ - Implementation: [0x04f439c341221Da7AE086b6F585e4Cd7a7E54622](https://etherscan.io/address/0x04f439c341221Da7AE086b6F585e4Cd7a7E54622) - Transaction: [0xfd5e59bb0472ffc5695cb9fcbe4031a416e1517467861de78657698e5e9ed329](https://etherscan.io/tx/0xfd5e59bb0472ffc5695cb9fcbe4031a416e1517467861de78657698e5e9ed329) +### Upgrade to v3.0.0 + +- Implementation: [0x01d34aeE72325F1d4A748f13C2169404523eCEE0](https://etherscan.io/address/0x01d34aeE72325F1d4A748f13C2169404523eCEE0) +- Transaction: [0x25a6dcc905d486cc76485301185579a697b764202b3faf1daf3b2bff89b539d9](https://etherscan.io/tx/0x25a6dcc905d486cc76485301185579a697b764202b3faf1daf3b2bff89b539d9) + ## StakeWiseToken - Contract: [0x48C3399719B582dD63eB5AADf12A40B4C3f52FA2](https://etherscan.io/address/0x48C3399719B582dD63eB5AADf12A40B4C3f52FA2) @@ -80,6 +95,11 @@ - Contract: [0x8a887282E67ff41d36C0b7537eAB035291461AcD](https://etherscan.io/address/0x8a887282E67ff41d36C0b7537eAB035291461AcD) - Transaction: [0x2eb5e24c36d9cb509472f992afa14521f8ad58a21fae500f633f85e07535f506](https://etherscan.io/tx/0x2eb5e24c36d9cb509472f992afa14521f8ad58a21fae500f633f85e07535f506) +### Upgrade to v3.0.0 + +- Contract: [0xF0C1670364d4b5c4e9dc8062cDd45068D9c678d6](https://etherscan.io/address/0xF0C1670364d4b5c4e9dc8062cDd45068D9c678d6) +- Transaction: [0x78502343487f8eba1c8226b9027aa189f3ca1ec97730873bcf924df99f4df0d0](https://etherscan.io/tx/0x78502343487f8eba1c8226b9027aa189f3ca1ec97730873bcf924df99f4df0d0) + ## Vesting Escrow - Contract: [0xaE678D2A911400a55e06f4A1F0C0B363F3eE2e42](https://etherscan.io/address/0xaE678D2A911400a55e06f4A1F0C0B363F3eE2e42) diff --git a/test/MerkleDistributor.test.js b/test/MerkleDistributor.test.js index accde955..9d96f41e 100644 --- a/test/MerkleDistributor.test.js +++ b/test/MerkleDistributor.test.js @@ -7,7 +7,7 @@ const { BN, constants, } = require('@openzeppelin/test-helpers'); -const { upgradeContracts } = require('../deployments'); +const { upgradeContracts, upgradeRewardEthToken } = require('../deployments'); const { contractSettings, contracts } = require('../deployments/settings'); const { stopImpersonatingAccount, @@ -16,7 +16,9 @@ const { setupOracleAccounts, setTotalRewards, setMerkleRoot, + addStakedEthToken, } = require('./utils'); +const { ethers } = require('hardhat'); const MerkleDistributor = artifacts.require('MerkleDistributor'); const StakeWiseToken = artifacts.require('StakeWiseToken'); @@ -63,141 +65,181 @@ const merkleProofs = { }, }; -contract('Merkle Distributor', ([beneficiary, anyone, ...otherAccounts]) => { - const admin = contractSettings.admin; - let merkleDistributor, - amount, - durationInBlocks, - token, - rewardEthToken, - stakedEthToken, - oracles, - oracleAccounts, - prevDistributorBalance, - pool; - - after(async () => stopImpersonatingAccount(admin)); - - beforeEach(async () => { - await impersonateAccount(admin); - await send.ether(anyone, admin, ether('5')); - - let upgradedContracts = await upgradeContracts(); - amount = ether('10'); - durationInBlocks = new BN(1000); - token = await StakeWiseToken.at(contracts.stakeWiseToken); - merkleDistributor = await MerkleDistributor.at( - upgradedContracts.merkleDistributor - ); - - rewardEthToken = await RewardEthToken.at(contracts.rewardEthToken); - stakedEthToken = await StakedEthToken.at(contracts.stakedEthToken); - merkleDistributor = await MerkleDistributor.at(contracts.merkleDistributor); - oracles = await Oracles.at(upgradedContracts.oracles); - oracleAccounts = await setupOracleAccounts({ - admin, +contract( + 'Merkle Distributor', + ([beneficiary, anyone, vault, ...otherAccounts]) => { + const admin = contractSettings.admin; + let merkleDistributor, + amount, + durationInBlocks, + token, + rewardEthToken, + stakedEthToken, oracles, - accounts: otherAccounts, - }); - pool = await Pool.at(contracts.pool); - prevDistributorBalance = await token.balanceOf(merkleDistributor.address); - }); - - afterEach(async () => resetFork()); - - it('not oracle fails to update merkle root', async () => { - await expectRevert( - merkleDistributor.setMerkleRoot(merkleRoot, 'link to merkle proofs', { - from: admin, - }), - 'MerkleDistributor: access denied' - ); - }); - - describe('periodically distribute', () => { - it('not admin fails to distribute tokens', async () => { - await expectRevert( - merkleDistributor.distributePeriodically( - admin, - token.address, - beneficiary, - amount, - durationInBlocks, - { - from: anyone, - } - ), - 'OwnablePausable: access denied' - ); - }); + oracleAccounts, + prevDistributorBalance, + pool; - it('fails to distribute tokens with zero amount', async () => { - await expectRevert( - merkleDistributor.distributePeriodically( - admin, - token.address, - beneficiary, - new BN(0), - durationInBlocks, - { - from: admin, - } - ), - 'MerkleDistributor: invalid amount' - ); - }); + after(async () => stopImpersonatingAccount(admin)); - it('fails to distribute tokens from zero address', async () => { - await expectRevert( - merkleDistributor.distributePeriodically( - constants.ZERO_ADDRESS, - token.address, - beneficiary, - amount, - durationInBlocks, - { - from: admin, - } - ), - 'ERC20: transfer from the zero address' + beforeEach(async () => { + await impersonateAccount(admin); + await send.ether(anyone, admin, ether('5')); + + let upgradedContracts = await upgradeContracts(vault); + amount = ether('10'); + durationInBlocks = new BN(1000); + token = await StakeWiseToken.at(contracts.stakeWiseToken); + merkleDistributor = await MerkleDistributor.at( + upgradedContracts.merkleDistributor ); - }); - it('fails to distribute tokens with max uint duration', async () => { - await expectRevert( - merkleDistributor.distributePeriodically( - admin, - token.address, - beneficiary, - amount, - constants.MAX_UINT256, - { - from: admin, - } - ), - 'MerkleDistributor: invalid blocks duration' + rewardEthToken = await RewardEthToken.at(contracts.rewardEthToken); + stakedEthToken = await StakedEthToken.at(contracts.stakedEthToken); + merkleDistributor = await MerkleDistributor.at( + contracts.merkleDistributor ); + oracles = await Oracles.at(upgradedContracts.oracles); + oracleAccounts = await setupOracleAccounts({ + admin, + oracles, + accounts: otherAccounts, + }); + pool = await Pool.at(contracts.pool); + prevDistributorBalance = await token.balanceOf(merkleDistributor.address); }); - it('fails to distribute tokens with zero duration', async () => { + afterEach(async () => resetFork()); + + it('not oracle fails to update merkle root', async () => { await expectRevert( - merkleDistributor.distributePeriodically( - admin, - token.address, - beneficiary, - amount, - new BN(0), - { - from: admin, - } - ), - 'MerkleDistributor: invalid blocks duration' + merkleDistributor.setMerkleRoot(merkleRoot, 'link to merkle proofs', { + from: admin, + }), + 'MerkleDistributor: access denied' ); }); - it('fails to distribute tokens without allowance', async () => { - await expectRevert( - merkleDistributor.distributePeriodically( + describe('periodically distribute', () => { + it('not admin fails to distribute tokens', async () => { + await expectRevert( + merkleDistributor.distributePeriodically( + admin, + token.address, + beneficiary, + amount, + durationInBlocks, + { + from: anyone, + } + ), + 'OwnablePausable: access denied' + ); + }); + + it('fails to distribute tokens with zero amount', async () => { + await expectRevert( + merkleDistributor.distributePeriodically( + admin, + token.address, + beneficiary, + new BN(0), + durationInBlocks, + { + from: admin, + } + ), + 'MerkleDistributor: invalid amount' + ); + }); + + it('fails to distribute tokens from zero address', async () => { + await expectRevert( + merkleDistributor.distributePeriodically( + constants.ZERO_ADDRESS, + token.address, + beneficiary, + amount, + durationInBlocks, + { + from: admin, + } + ), + 'ERC20: transfer from the zero address' + ); + }); + + it('fails to distribute tokens with max uint duration', async () => { + await expectRevert( + merkleDistributor.distributePeriodically( + admin, + token.address, + beneficiary, + amount, + constants.MAX_UINT256, + { + from: admin, + } + ), + 'MerkleDistributor: invalid blocks duration' + ); + }); + + it('fails to distribute tokens with zero duration', async () => { + await expectRevert( + merkleDistributor.distributePeriodically( + admin, + token.address, + beneficiary, + amount, + new BN(0), + { + from: admin, + } + ), + 'MerkleDistributor: invalid blocks duration' + ); + }); + + it('fails to distribute tokens without allowance', async () => { + await expectRevert( + merkleDistributor.distributePeriodically( + admin, + token.address, + beneficiary, + amount, + durationInBlocks, + { + from: admin, + } + ), + 'SafeMath: subtraction overflow' + ); + }); + + it('fails to distribute when paused', async () => { + await merkleDistributor.pause({ from: admin }); + await expectRevert( + merkleDistributor.distributePeriodically( + admin, + token.address, + beneficiary, + amount, + durationInBlocks, + { + from: admin, + } + ), + 'Pausable: paused' + ); + }); + + it('admin can distribute tokens', async () => { + await token.approve(merkleDistributor.address, amount, { + from: admin, + }); + + let receipt = await merkleDistributor.distributePeriodically( admin, token.address, beneficiary, @@ -206,114 +248,115 @@ contract('Merkle Distributor', ([beneficiary, anyone, ...otherAccounts]) => { { from: admin, } - ), - 'SafeMath: subtraction overflow' - ); - }); + ); - it('fails to distribute when paused', async () => { - await merkleDistributor.pause({ from: admin }); - await expectRevert( - merkleDistributor.distributePeriodically( - admin, - token.address, + let startBlock = new BN(receipt.receipt.blockNumber); + await expectEvent(receipt, 'PeriodicDistributionAdded', { + from: admin, + token: token.address, beneficiary, amount, - durationInBlocks, - { - from: admin, - } - ), - 'Pausable: paused' - ); + startBlock: startBlock, + endBlock: startBlock.add(durationInBlocks), + }); + expect( + await token.balanceOf(merkleDistributor.address) + ).to.bignumber.equal(prevDistributorBalance.add(amount)); + }); }); - it('admin can distribute tokens', async () => { - await token.approve(merkleDistributor.address, amount, { - from: admin, - }); + describe('one time distribute', () => { + const origin = '0x1111111111111111111111111111111111111111'; + const rewardsLink = + 'ipfs://QmehR8yCaKdHqHSxZMSJA5q2SWc8jTVCSKuVgbtqDEdXCH'; - let receipt = await merkleDistributor.distributePeriodically( - admin, - token.address, - beneficiary, - amount, - durationInBlocks, - { - from: admin, - } - ); + it('not admin fails to distribute tokens', async () => { + await expectRevert( + merkleDistributor.distributeOneTime( + admin, + origin, + token.address, + amount, + rewardsLink, + { + from: anyone, + } + ), + 'OwnablePausable: access denied' + ); + }); - let startBlock = new BN(receipt.receipt.blockNumber); - await expectEvent(receipt, 'PeriodicDistributionAdded', { - from: admin, - token: token.address, - beneficiary, - amount, - startBlock: startBlock, - endBlock: startBlock.add(durationInBlocks), + it('fails to distribute tokens with zero amount', async () => { + await expectRevert( + merkleDistributor.distributeOneTime( + admin, + origin, + token.address, + new BN(0), + rewardsLink, + { + from: admin, + } + ), + 'MerkleDistributor: invalid amount' + ); }); - expect( - await token.balanceOf(merkleDistributor.address) - ).to.bignumber.equal(prevDistributorBalance.add(amount)); - }); - }); - describe('one time distribute', () => { - const origin = '0x1111111111111111111111111111111111111111'; - const rewardsLink = 'ipfs://QmehR8yCaKdHqHSxZMSJA5q2SWc8jTVCSKuVgbtqDEdXCH'; + it('fails to distribute tokens from zero address', async () => { + await expectRevert( + merkleDistributor.distributeOneTime( + constants.ZERO_ADDRESS, + origin, + token.address, + amount, + rewardsLink, + { + from: admin, + } + ), + 'ERC20: transfer from the zero address' + ); + }); - it('not admin fails to distribute tokens', async () => { - await expectRevert( - merkleDistributor.distributeOneTime( - admin, - origin, - token.address, - amount, - rewardsLink, - { - from: anyone, - } - ), - 'OwnablePausable: access denied' - ); - }); + it('fails to distribute tokens without allowance', async () => { + await expectRevert( + merkleDistributor.distributeOneTime( + admin, + origin, + token.address, + amount, + rewardsLink, + { + from: admin, + } + ), + 'SafeMath: subtraction overflow' + ); + }); - it('fails to distribute tokens with zero amount', async () => { - await expectRevert( - merkleDistributor.distributeOneTime( - admin, - origin, - token.address, - new BN(0), - rewardsLink, - { - from: admin, - } - ), - 'MerkleDistributor: invalid amount' - ); - }); + it('fails to distribute when paused', async () => { + await merkleDistributor.pause({ from: admin }); + await expectRevert( + merkleDistributor.distributeOneTime( + admin, + origin, + token.address, + amount, + rewardsLink, + { + from: admin, + } + ), + 'Pausable: paused' + ); + }); - it('fails to distribute tokens from zero address', async () => { - await expectRevert( - merkleDistributor.distributeOneTime( - constants.ZERO_ADDRESS, - origin, - token.address, - amount, - rewardsLink, - { - from: admin, - } - ), - 'ERC20: transfer from the zero address' - ); - }); + it('admin can distribute tokens', async () => { + await token.approve(merkleDistributor.address, amount, { + from: admin, + }); - it('fails to distribute tokens without allowance', async () => { - await expectRevert( - merkleDistributor.distributeOneTime( + let receipt = await merkleDistributor.distributeOneTime( admin, origin, token.address, @@ -322,262 +365,109 @@ contract('Merkle Distributor', ([beneficiary, anyone, ...otherAccounts]) => { { from: admin, } - ), - 'SafeMath: subtraction overflow' - ); - }); + ); - it('fails to distribute when paused', async () => { - await merkleDistributor.pause({ from: admin }); - await expectRevert( - merkleDistributor.distributeOneTime( - admin, + await expectEvent(receipt, 'OneTimeDistributionAdded', { + from: admin, origin, - token.address, + token: token.address, amount, rewardsLink, - { - from: admin, - } - ), - 'Pausable: paused' - ); - }); - - it('admin can distribute tokens', async () => { - await token.approve(merkleDistributor.address, amount, { - from: admin, - }); - - let receipt = await merkleDistributor.distributeOneTime( - admin, - origin, - token.address, - amount, - rewardsLink, - { - from: admin, - } - ); - - await expectEvent(receipt, 'OneTimeDistributionAdded', { - from: admin, - origin, - token: token.address, - amount, - rewardsLink, + }); + expect( + await token.balanceOf(merkleDistributor.address) + ).to.bignumber.equal(prevDistributorBalance.add(amount)); }); - expect( - await token.balanceOf(merkleDistributor.address) - ).to.bignumber.equal(prevDistributorBalance.add(amount)); }); - }); - describe('claim', () => { - beforeEach(async () => { - // new rewards arrive - let totalRewards = (await rewardEthToken.totalRewards()).add(ether('10')); - await setTotalRewards({ - rewardEthToken, - oracles, - oracleAccounts, - pool, - totalRewards, + describe('claim', () => { + beforeEach(async () => { + // new rewards arrive + let totalRewards = (await rewardEthToken.totalRewards()).add( + ether('10') + ); + await setTotalRewards({ + rewardEthToken, + totalRewards, + vault, + }); }); - }); - - it('cannot claim when contract paused', async () => { - const { index, proof, amounts, tokens } = merkleProofs[account1]; - await merkleDistributor.pause({ from: admin }); - await expectRevert( - merkleDistributor.claim(index, account1, tokens, amounts, proof, { - from: anyone, - }), - 'Pausable: paused' - ); - }); - - it('cannot claim when merkle root updating', async () => { - // try to claim before merkle root update - const { index, proof, amounts, tokens } = merkleProofs[account1]; - await expectRevert( - merkleDistributor.claim(index, account1, tokens, amounts, proof, { - from: anyone, - }), - 'MerkleDistributor: merkle root updating' - ); - }); - it('cannot claim with invalid merkle proof', async () => { - await setMerkleRoot({ - merkleDistributor, - merkleRoot, - merkleProofs, - oracles, - oracleAccounts, - }); - const { index, amounts, tokens } = merkleProofs[account1]; - await expectRevert( - merkleDistributor.claim( - index, - account1, - tokens, - amounts, - merkleProofs[account2].proof, - { + it('cannot claim when contract paused', async () => { + const { index, proof, amounts, tokens } = merkleProofs[account1]; + await merkleDistributor.pause({ from: admin }); + await expectRevert( + merkleDistributor.claim(index, account1, tokens, amounts, proof, { from: anyone, - } - ), - 'MerkleDistributor: invalid proof' - ); - }); - - it('cannot claim twice', async () => { - await pool.setMinActivatingDeposit(constants.MAX_UINT256, { - from: admin, - }); - await pool.stake({ - from: anyone, - value: ether('1000'), - }); - await stakedEthToken.toggleRewards(anyone, true, { - from: admin, - }); - let totalDeposits = await stakedEthToken.totalDeposits(); - let totalRewards = await rewardEthToken.totalSupply(); - let periodReward = distributorEthReward - .mul(totalDeposits) - .div(ether('1000')); - let protocolFee = await rewardEthToken.protocolFee(); - totalRewards = totalRewards.add(periodReward); - totalRewards = totalRewards.add( - periodReward.mul(protocolFee).div(new BN(10000)) - ); - - await setTotalRewards({ - rewardEthToken, - oracles, - oracleAccounts, - pool, - totalRewards, - }); - - await token.transfer(merkleDistributor.address, distributorTokenReward, { - from: admin, - }); - await setMerkleRoot({ - merkleDistributor, - merkleRoot, - merkleProofs, - oracles, - oracleAccounts, - }); - - const { index, amounts, tokens, proof } = merkleProofs[account1]; - await merkleDistributor.claim(index, account1, tokens, amounts, proof, { - from: anyone, + }), + 'Pausable: paused' + ); }); - await expectRevert( - merkleDistributor.claim(index, account1, tokens, amounts, proof, { - from: anyone, - }), - 'MerkleDistributor: already claimed' - ); - }); - - it('can claim reward tokens', async () => { - await pool.setMinActivatingDeposit(constants.MAX_UINT256, { - from: admin, - }); - await pool.stake({ - from: anyone, - value: ether('1000'), - }); - await stakedEthToken.toggleRewards(anyone, true, { - from: admin, - }); - await setTotalRewards({ - rewardEthToken, - oracles, - oracleAccounts, - pool, - totalRewards: ether('100000'), + it('cannot claim when merkle root updating', async () => { + // try to claim before merkle root update + const { index, proof, amounts, tokens } = merkleProofs[account1]; + await expectRevert( + merkleDistributor.claim(index, account1, tokens, amounts, proof, { + from: anyone, + }), + 'MerkleDistributor: merkle root updating' + ); }); - await token.transfer(merkleDistributor.address, distributorTokenReward, { - from: admin, - }); - await setMerkleRoot({ - merkleDistributor, - merkleRoot, - merkleProofs, - oracles, - oracleAccounts, + it('cannot claim with invalid merkle proof', async () => { + await setMerkleRoot({ + merkleDistributor, + merkleRoot, + merkleProofs, + oracles, + oracleAccounts, + }); + const { index, amounts, tokens } = merkleProofs[account1]; + await expectRevert( + merkleDistributor.claim( + index, + account1, + tokens, + amounts, + merkleProofs[account2].proof, + { + from: anyone, + } + ), + 'MerkleDistributor: invalid proof' + ); }); - let distributorEthRewards = await rewardEthToken.balanceOf( - constants.ZERO_ADDRESS - ); - expect(distributorEthRewards).to.bignumber.greaterThan(new BN(0)); - let totalTransferredEthReward = new BN(0); - for (const [account, { index, proof, tokens, amounts }] of Object.entries( - merkleProofs - )) { - let balance1 = await rewardEthToken.balanceOf(account); - let balance2 = await token.balanceOf(account); - - const receipt = await merkleDistributor.claim( - index, - account, - tokens, - amounts, - proof, - { - from: anyone, - } + it('cannot claim twice', async () => { + await addStakedEthToken(stakedEthToken, anyone, ether('100')); + await stakedEthToken.toggleRewards(anyone, true, { + from: admin, + }); + let totalDeposits = await stakedEthToken.totalDeposits(); + let totalRewards = await rewardEthToken.totalSupply(); + let periodReward = distributorEthReward + .mul(totalDeposits) + .div(ether('100')); + let protocolFee = await rewardEthToken.protocolFee(); + totalRewards = totalRewards.add(periodReward); + totalRewards = totalRewards.add( + periodReward.mul(protocolFee).div(new BN(10000)) ); - expectEvent(receipt, 'Claimed', { - account, - index, - tokens, + + await setTotalRewards({ + rewardEthToken, + totalRewards, + vault, }); - await expectEvent.inTransaction( - receipt.tx, - RewardEthToken, - 'Transfer', + + await token.transfer( + merkleDistributor.address, + distributorTokenReward, { - from: constants.ZERO_ADDRESS, - to: account, - value: new BN(amounts[0]), + from: admin, } ); - expect(await merkleDistributor.isClaimed(index)).to.be.equal(true); - expect(await rewardEthToken.balanceOf(account)).to.be.bignumber.equal( - balance1.add(new BN(amounts[0])) - ); - totalTransferredEthReward = totalTransferredEthReward.add( - new BN(amounts[0]) - ); - expect( - await rewardEthToken.balanceOf(constants.ZERO_ADDRESS) - ).to.bignumber.equal( - distributorEthRewards.sub(totalTransferredEthReward) - ); - expect(await token.balanceOf(account)).to.be.bignumber.equal( - balance2.add(new BN(amounts[1])) - ); - } - }); - - describe('claiming within the same block', () => { - let multicallMock, - totalRewards, - activatedValidators, - rewardsSignatures, - merkleRootSignatures; - - beforeEach(async () => { await setMerkleRoot({ merkleDistributor, merkleRoot, @@ -586,75 +476,184 @@ contract('Merkle Distributor', ([beneficiary, anyone, ...otherAccounts]) => { oracleAccounts, }); - // deploy mocked oracle - multicallMock = await MulticallMock.new( - oracles.address, - stakedEthToken.address, - rewardEthToken.address, - merkleDistributor.address - ); - await oracles.addOracle(multicallMock.address, { - from: admin, - }); - - await pool.stake({ + const { index, amounts, tokens, proof } = merkleProofs[account1]; + await merkleDistributor.claim(index, account1, tokens, amounts, proof, { from: anyone, - value: ether('1000'), }); + + await expectRevert( + merkleDistributor.claim(index, account1, tokens, amounts, proof, { + from: anyone, + }), + 'MerkleDistributor: already claimed' + ); + }); + + it('can claim reward tokens', async () => { + await addStakedEthToken(stakedEthToken, anyone, ether('100')); await stakedEthToken.toggleRewards(anyone, true, { from: admin, }); - let totalDeposits = await stakedEthToken.totalDeposits(); - let protocolFee = await rewardEthToken.protocolFee(); - totalRewards = distributorEthReward - .mul(totalDeposits) - .div(ether('1000')); - totalRewards = totalRewards.add( - totalRewards.add(protocolFee.div(new BN(10000))) + await setTotalRewards({ + rewardEthToken, + pool, + vault, + totalRewards: ether('100000'), + }); + + await token.transfer( + merkleDistributor.address, + distributorTokenReward, + { + from: admin, + } ); - activatedValidators = await pool.activatedValidators(); - - // create rewards signature - let currentNonce = await oracles.currentRewardsNonce(); - let encoded = defaultAbiCoder.encode( - ['uint256', 'uint256', 'uint256'], - [ - currentNonce.toString(), - activatedValidators.toString(), - totalRewards.toString(), - ] + await setMerkleRoot({ + merkleDistributor, + merkleRoot, + merkleProofs, + oracles, + oracleAccounts, + }); + let distributorEthRewards = await rewardEthToken.balanceOf( + constants.ZERO_ADDRESS ); - let candidateId = hexlify(keccak256(encoded)); - rewardsSignatures = []; - for (const oracleAccount of oracleAccounts) { - rewardsSignatures.push( - await web3.eth.sign(candidateId, oracleAccount) - ); - } + expect(distributorEthRewards).to.bignumber.greaterThan(new BN(0)); - // create merkle root signature - encoded = defaultAbiCoder.encode( - ['uint256', 'string', 'bytes32'], - [currentNonce.add(new BN(1)).toString(), merkleProofs, merkleRoot] - ); - candidateId = hexlify(keccak256(encoded)); - merkleRootSignatures = []; - for (const oracleAccount of oracleAccounts) { - merkleRootSignatures.push( - await web3.eth.sign(candidateId, oracleAccount) + let totalTransferredEthReward = new BN(0); + for (const [ + account, + { index, proof, tokens, amounts }, + ] of Object.entries(merkleProofs)) { + let balance1 = await rewardEthToken.balanceOf(account); + let balance2 = await token.balanceOf(account); + + const receipt = await merkleDistributor.claim( + index, + account, + tokens, + amounts, + proof, + { + from: anyone, + } + ); + expectEvent(receipt, 'Claimed', { + account, + index, + tokens, + }); + await expectEvent.inTransaction( + receipt.tx, + RewardEthToken, + 'Transfer', + { + from: constants.ZERO_ADDRESS, + to: account, + value: new BN(amounts[0]), + } + ); + expect(await merkleDistributor.isClaimed(index)).to.be.equal(true); + expect(await rewardEthToken.balanceOf(account)).to.be.bignumber.equal( + balance1.add(new BN(amounts[0])) + ); + totalTransferredEthReward = totalTransferredEthReward.add( + new BN(amounts[0]) + ); + expect( + await rewardEthToken.balanceOf(constants.ZERO_ADDRESS) + ).to.bignumber.equal( + distributorEthRewards.sub(totalTransferredEthReward) + ); + expect(await token.balanceOf(account)).to.be.bignumber.equal( + balance2.add(new BN(amounts[1])) ); } }); - it('cannot claim after total rewards update in the same block', async () => { - const { index, amounts, tokens, proof } = merkleProofs[account1]; - await expectRevert( - multicallMock.updateTotalRewardsAndClaim( - { - totalRewards: totalRewards.toString(), - activatedValidators: activatedValidators.toString(), - signatures: rewardsSignatures, - }, + describe('claiming within the same block', () => { + const rewardsDelta = ether('10'); + let multicallMock, totalRewards, merkleRootSignatures; + + beforeEach(async () => { + await setMerkleRoot({ + merkleDistributor, + merkleRoot, + merkleProofs, + oracles, + oracleAccounts, + }); + + // deploy mocked oracle + multicallMock = await MulticallMock.new( + oracles.address, + stakedEthToken.address, + rewardEthToken.address, + merkleDistributor.address + ); + await oracles.addOracle(multicallMock.address, { + from: admin, + }); + const signer = await ethers.provider.getSigner( + contractSettings.admin + ); + await upgradeRewardEthToken(signer, multicallMock.address); + + await addStakedEthToken(stakedEthToken, anyone, ether('100')); + await stakedEthToken.toggleRewards(anyone, true, { + from: admin, + }); + let totalDeposits = await stakedEthToken.totalDeposits(); + let protocolFee = await rewardEthToken.protocolFee(); + totalRewards = distributorEthReward + .mul(totalDeposits) + .div(ether('100')); + totalRewards = totalRewards.add( + totalRewards.add(protocolFee.div(new BN(10000))) + ); + + // create merkle root signature + let currentNonce = await oracles.currentRewardsNonce(); + let encoded = defaultAbiCoder.encode( + ['uint256', 'string', 'bytes32'], + [currentNonce.toString(), merkleProofs, merkleRoot] + ); + + let candidateId = hexlify(keccak256(encoded)); + merkleRootSignatures = []; + for (const oracleAccount of oracleAccounts) { + merkleRootSignatures.push( + await web3.eth.sign(candidateId, oracleAccount) + ); + } + }); + + it('cannot claim after total rewards update in the same block', async () => { + const { index, amounts, tokens, proof } = merkleProofs[account1]; + const signer = await ethers.provider.getSigner( + contractSettings.admin + ); + await upgradeRewardEthToken(signer, multicallMock.address); + await expectRevert( + multicallMock.updateTotalRewardsAndClaim( + rewardsDelta, + index, + account1, + tokens, + amounts, + proof, + { + from: anyone, + } + ), + 'MerkleDistributor: merkle root updating' + ); + }); + + it('can claim before total rewards update in the same block', async () => { + const { index, amounts, tokens, proof } = merkleProofs[account1]; + await multicallMock.claimAndUpdateTotalRewards( + rewardsDelta, index, account1, tokens, @@ -663,42 +662,45 @@ contract('Merkle Distributor', ([beneficiary, anyone, ...otherAccounts]) => { { from: anyone, } - ), - 'MerkleDistributor: merkle root updating' - ); - }); + ); + }); - it('can claim before total rewards update in the same block', async () => { - const { index, amounts, tokens, proof } = merkleProofs[account1]; - await multicallMock.claimAndUpdateTotalRewards( - { - totalRewards: totalRewards.toString(), - activatedValidators: activatedValidators.toString(), - signatures: rewardsSignatures, - }, - index, - account1, - tokens, - amounts, - proof, - { - from: anyone, - } - ); - }); + it('cannot claim before merkle root update in the same block', async () => { + const { index, amounts, tokens, proof } = merkleProofs[account1]; + const signer = await ethers.provider.getSigner( + contractSettings.admin + ); + await upgradeRewardEthToken(signer, vault); + await rewardEthToken.updateTotalRewards(rewardsDelta, { + from: vault, + }); + await expectRevert( + multicallMock.claimAndUpdateMerkleRoot( + { merkleRoot, merkleProofs, signatures: merkleRootSignatures }, + index, + account1, + tokens, + amounts, + proof, + { + from: anyone, + } + ), + 'MerkleDistributor: merkle root updating' + ); + }); - it('cannot claim before merkle root update in the same block', async () => { - const { index, amounts, tokens, proof } = merkleProofs[account1]; - await oracles.submitRewards( - totalRewards, - activatedValidators, - rewardsSignatures, - { - from: oracleAccounts[0], - } - ); - await expectRevert( - multicallMock.claimAndUpdateMerkleRoot( + it('can claim after merkle root update in the same block', async () => { + const signer = await ethers.provider.getSigner( + contractSettings.admin + ); + await upgradeRewardEthToken(signer, vault); + await rewardEthToken.updateTotalRewards(rewardsDelta, { + from: vault, + }); + + const { index, amounts, tokens, proof } = merkleProofs[account1]; + await multicallMock.updateMerkleRootAndClaim( { merkleRoot, merkleProofs, signatures: merkleRootSignatures }, index, account1, @@ -708,63 +710,39 @@ contract('Merkle Distributor', ([beneficiary, anyone, ...otherAccounts]) => { { from: anyone, } - ), - 'MerkleDistributor: merkle root updating' - ); + ); + }); }); + }); - it('can claim after merkle root update in the same block', async () => { - await oracles.submitRewards( - totalRewards, - activatedValidators, - rewardsSignatures, - { - from: oracleAccounts[0], - } - ); - const { index, amounts, tokens, proof } = merkleProofs[account1]; - await multicallMock.updateMerkleRootAndClaim( - { merkleRoot, merkleProofs, signatures: merkleRootSignatures }, - index, - account1, - tokens, - amounts, - proof, - { + describe('upgrading', () => { + it('fails to upgrade with not admin privilege', async () => { + await expectRevert( + merkleDistributor.upgrade(oracles.address, { from: anyone, - } + }), + 'OwnablePausable: access denied' ); }); - }); - }); - - describe('upgrading', () => { - it('fails to upgrade with not admin privilege', async () => { - await expectRevert( - merkleDistributor.upgrade(oracles.address, { - from: anyone, - }), - 'OwnablePausable: access denied' - ); - }); - it('fails to upgrade when not paused', async () => { - await expectRevert( - merkleDistributor.upgrade(oracles.address, { - from: admin, - }), - 'Pausable: not paused' - ); - }); + it('fails to upgrade when not paused', async () => { + await expectRevert( + merkleDistributor.upgrade(oracles.address, { + from: admin, + }), + 'Pausable: not paused' + ); + }); - it('fails to upgrade twice', async () => { - await merkleDistributor.pause({ from: admin }); - await expectRevert( - merkleDistributor.upgrade(oracles.address, { - from: admin, - }), - 'MerkleDistributor: invalid Oracles address' - ); + it('fails to upgrade twice', async () => { + await merkleDistributor.pause({ from: admin }); + await expectRevert( + merkleDistributor.upgrade(oracles.address, { + from: admin, + }), + 'MerkleDistributor: invalid Oracles address' + ); + }); }); - }); -}); + } +); diff --git a/test/oracles/Oracles.test.js b/test/oracles/Oracles.test.js index d55acfc0..a81b0084 100644 --- a/test/oracles/Oracles.test.js +++ b/test/oracles/Oracles.test.js @@ -4,9 +4,7 @@ const { expectEvent, expectRevert, ether, - BN, send, - balance, } = require('@openzeppelin/test-helpers'); const { impersonateAccount, @@ -16,29 +14,21 @@ const { setTotalRewards, } = require('../utils'); const { contractSettings } = require('../../deployments/settings'); -const { upgradeContracts } = require('../../deployments'); const { - depositDataMerkleRoot, - depositData, -} = require('../pool/depositDataMerkleRoot'); + upgradeContracts, + upgradeRewardEthToken, +} = require('../../deployments'); +const { ethers } = require('hardhat'); const RewardEthToken = artifacts.require('RewardEthToken'); const Oracles = artifacts.require('Oracles'); -const Pool = artifacts.require('Pool'); const MulticallMock = artifacts.require('MulticallMock'); const MerkleDistributor = artifacts.require('MerkleDistributor'); -const PoolValidators = artifacts.require('PoolValidators'); -const iDepositContract = artifacts.require('IDepositContract'); -contract('Oracles', ([_, anyone, operator, ...accounts]) => { +contract('Oracles', ([_, anyone, ...accounts]) => { let admin = contractSettings.admin; - let oracles, - rewardEthToken, - pool, - merkleDistributor, - poolValidators, - contracts; - let [oracle, anotherOracle] = accounts; + let oracles, rewardEthToken, merkleDistributor, contracts; + let [oracle, anotherOracle, vault] = accounts; after(async () => stopImpersonatingAccount(admin)); @@ -46,12 +36,10 @@ contract('Oracles', ([_, anyone, operator, ...accounts]) => { await impersonateAccount(admin); await send.ether(anyone, admin, ether('5')); - contracts = await upgradeContracts(); + contracts = await upgradeContracts(vault); oracles = await Oracles.at(contracts.oracles); - pool = await Pool.at(contracts.pool); rewardEthToken = await RewardEthToken.at(contracts.rewardEthToken); merkleDistributor = await MerkleDistributor.at(contracts.merkleDistributor); - poolValidators = await PoolValidators.at(contracts.poolValidators); }); afterEach(async () => resetFork()); @@ -138,148 +126,6 @@ contract('Oracles', ([_, anyone, operator, ...accounts]) => { }); }); - describe('rewards voting', () => { - let prevTotalRewards, - newTotalRewards, - currentNonce, - newActivatedValidators, - oracleAccounts, - candidateId, - feesEscrowBalance, - signatures; - - beforeEach(async () => { - feesEscrowBalance = await balance.current(contracts.feesEscrow); - oracleAccounts = await setupOracleAccounts({ oracles, accounts, admin }); - prevTotalRewards = await rewardEthToken.totalRewards(); - newTotalRewards = prevTotalRewards.add(ether('10')); - currentNonce = await oracles.currentRewardsNonce(); - newActivatedValidators = (await pool.activatedValidators()).add( - await pool.pendingValidators() - ); - - let encoded = defaultAbiCoder.encode( - ['uint256', 'uint256', 'uint256'], - [ - currentNonce.toString(), - newActivatedValidators.toString(), - newTotalRewards.toString(), - ] - ); - candidateId = keccak256(encoded); - - signatures = []; - for (const oracleAccount of oracleAccounts) { - signatures.push(await web3.eth.sign(candidateId, oracleAccount)); - } - }); - - it('fails to submit when contract is paused', async () => { - await oracles.pause({ from: admin }); - expect(await oracles.paused()).equal(true); - - await expectRevert( - oracles.submitRewards( - newTotalRewards, - newActivatedValidators, - signatures, - { - from: oracleAccounts[0], - } - ), - 'Pausable: paused' - ); - }); - - it('fails to submit with not enough signatures', async () => { - await expectRevert( - oracles.submitRewards( - newTotalRewards, - newActivatedValidators, - signatures.slice(signatures.length - 1), - { - from: oracleAccounts[0], - } - ), - 'Oracles: invalid number of signatures' - ); - }); - - it('fails to submit with invalid signature', async () => { - signatures[0] = await web3.eth.sign(candidateId, anyone); - await expectRevert( - oracles.submitRewards( - newTotalRewards, - newActivatedValidators, - signatures, - { - from: oracleAccounts[0], - } - ), - 'Oracles: invalid signer' - ); - }); - - it('fails to submit with repeated signature', async () => { - let signature = signatures[0]; - await expectRevert( - oracles.submitRewards( - newTotalRewards, - newActivatedValidators, - Array(oracleAccounts.length).fill(signature), - { - from: oracleAccounts[0], - } - ), - 'Oracles: repeated signature' - ); - }); - - it('fails to submit without oracle role assigned', async () => { - await expectRevert( - oracles.submitRewards( - newTotalRewards, - newActivatedValidators, - signatures, - { - from: anyone, - } - ), - 'Oracles: access denied' - ); - }); - - it('submits data with enough signatures', async () => { - let receipt = await oracles.submitRewards( - newTotalRewards, - newActivatedValidators, - signatures, - { - from: oracleAccounts[0], - } - ); - - // check signatures - for (const oracleAccount of oracleAccounts) { - expectEvent(receipt, 'RewardsVoteSubmitted', { - oracle: oracleAccount, - totalRewards: newTotalRewards, - activatedValidators: newActivatedValidators, - nonce: currentNonce, - }); - } - - // check values updates - expect(await rewardEthToken.totalRewards()).to.bignumber.equal( - newTotalRewards.add(feesEscrowBalance) - ); - expect(await pool.activatedValidators()).to.bignumber.equal( - newActivatedValidators - ); - expect(await pool.pendingValidators()).to.bignumber.equal(new BN(0)); - }); - }); - describe('merkle root voting', () => { const merkleRoot = '0xa3e724fce28a564a7908e40994bd8f48ed4470ffcab4c135fe661bcf5b15afe6'; @@ -291,10 +137,8 @@ contract('Oracles', ([_, anyone, operator, ...accounts]) => { let totalRewards = (await rewardEthToken.totalRewards()).add(ether('10')); oracleAccounts = await setupOracleAccounts({ oracles, accounts, admin }); await setTotalRewards({ + vault, rewardEthToken, - oracles, - oracleAccounts, - pool, totalRewards, }); @@ -415,38 +259,22 @@ contract('Oracles', ([_, anyone, operator, ...accounts]) => { it('fails to vote for total rewards and merkle root in same block', async () => { // deploy mocked oracle - let mockedOracle = await MulticallMock.new( + let multicallMock = await MulticallMock.new( oracles.address, contracts.stakedEthToken, contracts.rewardEthToken, merkleDistributor.address ); - await oracles.addOracle(mockedOracle.address, { + await oracles.addOracle(multicallMock.address, { from: admin, }); - - let totalRewards = (await rewardEthToken.totalRewards()).add(ether('10')); - let activatedValidators = await pool.activatedValidators(); - - // create rewards signatures - let currentNonce = await oracles.currentRewardsNonce(); - let encoded = defaultAbiCoder.encode( - ['uint256', 'uint256', 'uint256'], - [ - currentNonce.toString(), - activatedValidators.toString(), - totalRewards.toString(), - ] - ); - candidateId = keccak256(encoded); - let rewardSignatures = []; - for (const oracleAccount of oracleAccounts) { - rewardSignatures.push(await web3.eth.sign(candidateId, oracleAccount)); - } + const signer = await ethers.provider.getSigner(contractSettings.admin); + await upgradeRewardEthToken(signer, multicallMock.address); + const rewardsDelta = ether('10'); // create merkle root signatures currentNonce = await oracles.currentRewardsNonce(); - encoded = defaultAbiCoder.encode( + let encoded = defaultAbiCoder.encode( ['uint256', 'string', 'bytes32'], [currentNonce.toString(), merkleProofs, merkleRoot] ); @@ -459,12 +287,8 @@ contract('Oracles', ([_, anyone, operator, ...accounts]) => { } await expectRevert( - mockedOracle.updateTotalRewardsAndMerkleRoot( - { - totalRewards: totalRewards.toString(), - activatedValidators: activatedValidators.toString(), - signatures: rewardSignatures, - }, + multicallMock.updateTotalRewardsAndMerkleRoot( + rewardsDelta, { merkleRoot, merkleProofs, @@ -478,168 +302,4 @@ contract('Oracles', ([_, anyone, operator, ...accounts]) => { ); }); }); - - describe('validator voting', () => { - const depositDataMerkleProofs = - 'ipfs://QmehR8yCaKdHqHSxZMSJA5q2SWc8jTVCSKuVgbtqDEdXCH'; - let currentNonce, - oracleAccounts, - candidateId, - signatures, - validatorsDepositRoot; - let validatorsDepositData = [ - { - operator, - withdrawalCredentials: depositData[0].withdrawalCredentials, - depositDataRoot: depositData[0].depositDataRoot, - publicKey: depositData[0].publicKey, - signature: depositData[0].signature, - }, - { - operator, - withdrawalCredentials: depositData[1].withdrawalCredentials, - depositDataRoot: depositData[1].depositDataRoot, - publicKey: depositData[1].publicKey, - signature: depositData[1].signature, - }, - ]; - let merkleProofs = [depositData[0].merkleProof, depositData[1].merkleProof]; - - beforeEach(async () => { - await poolValidators.addOperator( - operator, - depositDataMerkleRoot, - depositDataMerkleProofs, - { - from: admin, - } - ); - await poolValidators.commitOperator({ - from: operator, - }); - oracleAccounts = await setupOracleAccounts({ oracles, accounts, admin }); - currentNonce = await oracles.currentValidatorsNonce(); - - let depositContract = await iDepositContract.at( - await pool.validatorRegistration() - ); - validatorsDepositRoot = await depositContract.get_deposit_root(); - - let encoded = defaultAbiCoder.encode( - [ - 'uint256', - 'tuple(address operator,bytes32 withdrawalCredentials,bytes32 depositDataRoot,bytes publicKey,bytes signature)[]', - 'bytes32', - ], - [currentNonce.toString(), validatorsDepositData, validatorsDepositRoot] - ); - candidateId = keccak256(encoded); - - signatures = []; - for (const oracleAccount of oracleAccounts) { - signatures.push(await web3.eth.sign(candidateId, oracleAccount)); - } - }); - - it('fails to submit when contract is paused', async () => { - await oracles.pause({ from: admin }); - expect(await oracles.paused()).equal(true); - - await expectRevert( - oracles.registerValidators( - validatorsDepositData, - merkleProofs, - validatorsDepositRoot, - signatures, - { - from: oracleAccounts[0], - } - ), - 'Pausable: paused' - ); - }); - - it('fails to submit with not enough signatures', async () => { - await expectRevert( - oracles.registerValidators( - validatorsDepositData, - merkleProofs, - validatorsDepositRoot, - signatures.slice(signatures.length - 1), - { - from: oracleAccounts[0], - } - ), - 'Oracles: invalid number of signatures' - ); - }); - - it('fails to submit with invalid signature', async () => { - signatures[0] = await web3.eth.sign(candidateId, anyone); - await expectRevert( - oracles.registerValidators( - validatorsDepositData, - merkleProofs, - validatorsDepositRoot, - signatures, - { - from: oracleAccounts[0], - } - ), - 'Oracles: invalid signer' - ); - }); - - it('fails to submit with repeated signature', async () => { - let signature = signatures[0]; - await expectRevert( - oracles.registerValidators( - validatorsDepositData, - merkleProofs, - validatorsDepositRoot, - Array(oracleAccounts.length).fill(signature), - { - from: oracleAccounts[0], - } - ), - 'Oracles: repeated signature' - ); - }); - - it('fails to submit without oracle role assigned', async () => { - await expectRevert( - oracles.registerValidators( - validatorsDepositData, - merkleProofs, - validatorsDepositRoot, - signatures, - { - from: anyone, - } - ), - 'Oracles: access denied' - ); - }); - - it('can vote for multiple validators', async () => { - await pool.stake({ - from: anyone, - value: ether('64'), - }); - let receipt = await oracles.registerValidators( - validatorsDepositData, - merkleProofs, - validatorsDepositRoot, - signatures, - { - from: oracleAccounts[0], - } - ); - await expectEvent(receipt, 'RegisterValidatorsVoteSubmitted', { - sender: oracleAccounts[0], - oracles: oracleAccounts, - nonce: currentNonce, - }); - }); - }); }); diff --git a/test/pool/FeesEscrow.test.js b/test/pool/FeesEscrow.test.js index 1cf8c082..acbf8c9c 100644 --- a/test/pool/FeesEscrow.test.js +++ b/test/pool/FeesEscrow.test.js @@ -16,6 +16,7 @@ const FeesEscrow = artifacts.require('FeesEscrow'); let feesEscrow; let pool; +let poolEscrow; let rewardEthToken; contract('FeesEscrow', (accounts) => { @@ -32,6 +33,7 @@ contract('FeesEscrow', (accounts) => { ); feesEscrow = await FeesEscrow.at(upgradedContracts.feesEscrow); pool = await Pool.at(upgradedContracts.pool); + poolEscrow = upgradedContracts.poolEscrow; // Zero balance for Pool contract before each test await network.provider.send('hardhat_setBalance', [ @@ -42,42 +44,42 @@ contract('FeesEscrow', (accounts) => { afterEach(async () => resetFork()); - it('transferToPool from RewardEthToken', async () => { - await impersonateAccount(contracts.oracles); - const oraclesSigner = await ethers.getSigner(contracts.oracles); - const feesEscrowBalance = await balance.current(contracts.feesEscrow); + it('transfer fees to pool escrow', async () => { + await impersonateAccount(contracts.vault); + const vaultSigner = await ethers.getSigner(contracts.vault); const feesAmount = ether('1'); - // Ensure zero balances before miner's reward distribution to FeesEscrow contract const poolBalanceBefore = await balance.current(pool.address); - expect(poolBalanceBefore.toString()).to.be.bignumber.equal(new BN('0')); + const poolEscrowBalanceBefore = await balance.current(poolEscrow); // Send fees from "validator" to FeesEscrow contract await send.ether(sender, feesEscrow.address, feesAmount.toString()); + const feesEscrowBalanceBefore = await balance.current(feesEscrow.address); // set oracles balance to call rewardEthToken.updateTotalRewards() await ethers.provider.send('hardhat_setBalance', [ - oraclesSigner.address, + vaultSigner.address, '0x100000000000000000', ]); - const newTotalRewards = ether('100000'); + const rewardsDelta = ether('10'); await rewardEthToken - .connect(oraclesSigner) - .updateTotalRewards(newTotalRewards.toString()); + .connect(vaultSigner) + .updateTotalRewards(rewardsDelta.toString()); - // Ensure all fees transferred from FeesEscrow contract to Pool contract - const poolBalanceAfter = await balance.current(pool.address); - expect(poolBalanceAfter.toString()).to.be.bignumber.equal( - poolBalanceBefore.add(feesEscrowBalance).add(feesAmount).toString() - ); - - const feesEscrowBalanceAfterTransfer = await balance.current( - feesEscrow.address + // Ensure all fees transferred to Pool escrow + expect(await balance.current(pool.address)).to.be.bignumber.equal( + new BN('0') ); - expect(feesEscrowBalanceAfterTransfer.toString()).to.be.bignumber.equal( + expect(await balance.current(feesEscrow.address)).to.be.bignumber.equal( new BN('0') ); + const poolEscrowBalanceAfter = await balance.current(poolEscrow); + expect(poolEscrowBalanceAfter).to.be.bignumber.equal( + poolEscrowBalanceBefore + .add(feesEscrowBalanceBefore) + .add(poolBalanceBefore) + ); }); it('transferToPool from invalid caller', async () => { diff --git a/test/pool/PoolEscrow.test.js b/test/pool/PoolEscrow.test.js index b8f1e811..1af3fc14 100644 --- a/test/pool/PoolEscrow.test.js +++ b/test/pool/PoolEscrow.test.js @@ -4,7 +4,6 @@ const { expectEvent, ether, send, - BN, constants, balance, } = require('@openzeppelin/test-helpers'); @@ -21,6 +20,7 @@ const PoolEscrow = artifacts.require('PoolEscrow'); contract('PoolEscrow', ([anyone, newOwner, payee]) => { const owner = contractSettings.admin; let poolEscrow; + let poolEscrowBalance; after(async () => stopImpersonatingAccount(owner)); @@ -30,6 +30,7 @@ contract('PoolEscrow', ([anyone, newOwner, payee]) => { let contracts = await upgradeContracts(); poolEscrow = await PoolEscrow.at(contracts.poolEscrow); + poolEscrowBalance = await balance.current(poolEscrow.address); }); afterEach(async () => resetFork()); @@ -41,7 +42,7 @@ contract('PoolEscrow', ([anyone, newOwner, payee]) => { it('can receive ETH transfers', async () => { await send.ether(anyone, poolEscrow.address, ether('5')); expect(await balance.current(poolEscrow.address)).to.bignumber.equal( - ether('5') + poolEscrowBalance.add(ether('5')) ); }); @@ -146,7 +147,7 @@ contract('PoolEscrow', ([anyone, newOwner, payee]) => { amount, }); expect(await balance.current(poolEscrow.address)).to.bignumber.equal( - new BN(0) + poolEscrowBalance ); expect(await balance.current(payee)).to.bignumber.equal( payeeBalance.add(amount) @@ -178,7 +179,7 @@ contract('PoolEscrow', ([anyone, newOwner, payee]) => { it('fails to withdraw ether when not enough balance', async () => { let amount = ether('5'); await expectRevert( - poolEscrow.withdraw(payee, amount, { + poolEscrow.withdraw(payee, poolEscrowBalance.add(amount), { from: owner, }), 'Address: insufficient balance' diff --git a/test/pool/PoolValidators.test.js b/test/pool/PoolValidators.test.js deleted file mode 100644 index c2dbf397..00000000 --- a/test/pool/PoolValidators.test.js +++ /dev/null @@ -1,495 +0,0 @@ -const { - expectRevert, - expectEvent, - ether, - balance, - send, - constants, - BN, -} = require('@openzeppelin/test-helpers'); -const { keccak256, defaultAbiCoder } = require('ethers/lib/utils'); -const { upgradeContracts } = require('../../deployments'); -const { contractSettings } = require('../../deployments/settings'); -const { - registerValidators, - setupOracleAccounts, - stopImpersonatingAccount, - impersonateAccount, - resetFork, - checkValidatorRegistered, -} = require('../utils'); -const { - depositData, - depositDataMerkleRoot, -} = require('./depositDataMerkleRoot'); - -const Pool = artifacts.require('Pool'); -const PoolValidators = artifacts.require('PoolValidators'); -const Oracles = artifacts.require('Oracles'); -const IDepositContract = artifacts.require('IDepositContract'); - -contract('Pool Validators', (accounts) => { - const admin = contractSettings.admin; - const validatorDeposit = ether('32'); - const depositDataMerkleProofs = - 'ipfs://QmSTP443zR6oKnYVRE23RARyuuzwhhaidUiSXyRTsw3pDs'; - let pool, - validators, - validatorDepositAmount, - oracleAccounts, - oracles, - depositContract, - validatorsDepositRoot; - let [operator, anyone, ...otherAccounts] = accounts; - - after(async () => stopImpersonatingAccount(admin)); - - beforeEach(async () => { - await impersonateAccount(admin); - await send.ether(anyone, admin, ether('5')); - - let upgradedContracts = await upgradeContracts(); - pool = await Pool.at(upgradedContracts.pool); - depositContract = await IDepositContract.at( - await pool.validatorRegistration() - ); - validatorDepositAmount = await pool.VALIDATOR_TOTAL_DEPOSIT(); - validatorsDepositRoot = await depositContract.get_deposit_root(); - - validators = await PoolValidators.at(upgradedContracts.poolValidators); - - // collect validator deposit - let poolBalance = await balance.current(pool.address); - let depositAmount = validatorDeposit.sub(poolBalance.mod(validatorDeposit)); - await pool.stake({ - from: anyone, - value: depositAmount, - }); - - oracles = await Oracles.at(upgradedContracts.oracles); - oracleAccounts = await setupOracleAccounts({ - admin, - oracles, - accounts: otherAccounts, - }); - }); - - afterEach(async () => resetFork()); - - describe('add operator', () => { - it('fails to add with not admin privilege', async () => { - await expectRevert( - validators.addOperator( - operator, - depositDataMerkleRoot, - depositDataMerkleProofs, - { - from: anyone, - } - ), - 'OwnablePausable: access denied' - ); - }); - - it('fails to add with zero operator address', async () => { - await expectRevert( - validators.addOperator( - constants.ZERO_ADDRESS, - depositDataMerkleRoot, - depositDataMerkleProofs, - { - from: admin, - } - ), - 'PoolValidators: invalid operator' - ); - }); - - it('fails to add with invalid merkle root', async () => { - await expectRevert( - validators.addOperator( - operator, - constants.ZERO_BYTES32, - depositDataMerkleProofs, - { - from: admin, - } - ), - 'PoolValidators: invalid merkle root' - ); - }); - - it('fails to add with invalid merkle proofs', async () => { - await expectRevert( - validators.addOperator(operator, depositDataMerkleRoot, '', { - from: admin, - }), - 'PoolValidators: invalid merkle proofs' - ); - }); - - it('can update existing operator', async () => { - await validators.addOperator( - operator, - depositDataMerkleRoot, - depositDataMerkleProofs, - { - from: admin, - } - ); - - let depositDataMerkleRoot2 = - '0x2a6d4eed3ba81bd99efdfd31333e244bb84989cfadbf9ddbf8fabd7296099bc0'; - - let receipt = await validators.addOperator( - operator, - depositDataMerkleRoot2, - depositDataMerkleProofs, - { - from: admin, - } - ); - - await expectEvent(receipt, 'OperatorAdded', { - operator, - depositDataMerkleRoot: depositDataMerkleRoot2, - depositDataMerkleProofs, - }); - - let _operator = await validators.getOperator(operator); - expect(_operator[0]).to.equal(depositDataMerkleRoot2); - expect(_operator[1]).to.equal(false); - }); - - it('can add new operator', async () => { - let receipt = await validators.addOperator( - operator, - depositDataMerkleRoot, - depositDataMerkleProofs, - { - from: admin, - } - ); - - await expectEvent(receipt, 'OperatorAdded', { - operator, - depositDataMerkleRoot, - depositDataMerkleProofs, - }); - - let _operator = await validators.getOperator(operator); - expect(_operator[0]).to.equal(depositDataMerkleRoot); - expect(_operator[1]).to.equal(false); - }); - }); - - describe('remove operator', () => { - beforeEach(async () => { - await validators.addOperator( - operator, - depositDataMerkleRoot, - depositDataMerkleProofs, - { - from: admin, - } - ); - }); - - it('fails to remove by user other than admin and operator', async () => { - await expectRevert( - validators.removeOperator(operator, { - from: anyone, - }), - 'PoolValidators: access denied' - ); - }); - - it('fails to remove not existing operator', async () => { - await expectRevert( - validators.removeOperator(anyone, { - from: admin, - }), - 'PoolValidators: invalid operator' - ); - }); - - it('operator or admin can remove operator', async () => { - let receipt = await validators.removeOperator(operator, { - from: admin, - }); - - await expectEvent(receipt, 'OperatorRemoved', { - sender: admin, - operator, - }); - - let _operator = await validators.getOperator(operator); - expect(_operator[0]).to.equal(constants.ZERO_BYTES32); - expect(_operator[1]).to.equal(false); - }); - }); - - describe('commit operator', () => { - beforeEach(async () => { - await validators.addOperator( - operator, - depositDataMerkleRoot, - depositDataMerkleProofs, - { - from: admin, - } - ); - }); - - it('fails to commit invalid operator', async () => { - await expectRevert( - validators.commitOperator({ - from: anyone, - }), - 'PoolValidators: invalid operator' - ); - }); - - it('fails to commit operator twice', async () => { - await validators.commitOperator({ - from: operator, - }); - await expectRevert( - validators.commitOperator({ - from: operator, - }), - 'PoolValidators: invalid operator' - ); - }); - - it('can commit operator', async () => { - let receipt = await validators.commitOperator({ - from: operator, - }); - - await expectEvent(receipt, 'OperatorCommitted', { - operator, - }); - }); - }); - - describe('register validators', () => { - let validatorDepositData = { - operator, - withdrawalCredentials: depositData[0].withdrawalCredentials, - depositDataRoot: depositData[0].depositDataRoot, - publicKey: depositData[0].publicKey, - signature: depositData[0].signature, - }; - let merkleProof = depositData[0].merkleProof; - - beforeEach(async () => { - await validators.addOperator( - operator, - depositDataMerkleRoot, - depositDataMerkleProofs, - { - from: admin, - } - ); - }); - - it('fails to register validator by not oracles', async () => { - await expectRevert( - validators.registerValidator(validatorDepositData, merkleProof, { - from: anyone, - }), - 'PoolValidators: access denied' - ); - }); - - it('fails to register validator for not committed operator', async () => { - await expectRevert( - registerValidators({ - depositData: [validatorDepositData], - merkleProofs: [merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot, - }), - 'PoolValidators: invalid operator' - ); - }); - - it('fails to register validator twice', async () => { - await validators.commitOperator({ - from: operator, - }); - - await expectRevert( - registerValidators({ - depositData: [validatorDepositData, validatorDepositData], - merkleProofs: [merkleProof, merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot, - }), - 'PoolValidators: validator already registered' - ); - }); - - it('fails to register for invalid operator', async () => { - await validators.commitOperator({ - from: operator, - }); - - await expectRevert( - registerValidators({ - depositData: [{ ...validatorDepositData, operator: anyone }], - merkleProofs: [merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot, - }), - 'PoolValidators: invalid operator' - ); - }); - - it('fails to register for invalid deposit data', async () => { - await validators.commitOperator({ - from: operator, - }); - await expectRevert( - registerValidators({ - depositData: [ - { - ...validatorDepositData, - depositDataRoot: constants.ZERO_BYTES32, - }, - ], - merkleProofs: [merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot, - }), - 'PoolValidators: invalid merkle proof' - ); - }); - - it('fails to register with invalid validators deposit root', async () => { - await validators.commitOperator({ - from: operator, - }); - await expectRevert( - registerValidators({ - depositData: [validatorDepositData], - merkleProofs: [merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot: keccak256('0x6be4000000000000'), - }), - 'Oracles: invalid validators deposit root' - ); - }); - - it('oracles can register one validator', async () => { - await validators.commitOperator({ - from: operator, - }); - - let poolBalance = await balance.current(pool.address); - let receipt = await registerValidators({ - depositData: [validatorDepositData], - merkleProofs: [merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot, - }); - - await expectEvent.inTransaction(receipt.tx, Pool, 'ValidatorRegistered', { - operator, - publicKey: validatorDepositData.publicKey, - }); - expect( - await validators.isValidatorRegistered( - keccak256( - defaultAbiCoder.encode(['bytes'], [validatorDepositData.publicKey]) - ) - ) - ).to.equal(true); - let _operator = await validators.getOperator(operator); - expect(_operator[1]).to.equal(true); - expect(await balance.current(pool.address)).to.bignumber.equal( - poolBalance.sub(validatorDepositAmount) - ); - await checkValidatorRegistered({ - transaction: receipt.tx, - pubKey: validatorDepositData.publicKey, - withdrawalCredentials: validatorDepositData.withdrawalCredentials, - signature: validatorDepositData.signature, - validatorDepositAmount, - }); - }); - - it('oracles can register multiple validators', async () => { - await validators.commitOperator({ - from: operator, - }); - await pool.stake({ - from: anyone, - value: ether('32').mul(new BN(depositData.length)), - }); - - let poolBalance = await balance.current(pool.address); - let validatorsDepositData = []; - let merkleProofs = []; - for (let i = 0; i < depositData.length; i++) { - validatorsDepositData.push({ - operator, - withdrawalCredentials: depositData[i].withdrawalCredentials, - depositDataRoot: depositData[i].depositDataRoot, - publicKey: depositData[i].publicKey, - signature: depositData[i].signature, - }); - merkleProofs.push(depositData[i].merkleProof); - } - let receipt = await registerValidators({ - depositData: validatorsDepositData, - merkleProofs, - oracles, - oracleAccounts, - validatorsDepositRoot, - }); - - for (let i = 0; i < depositData.length; i++) { - await expectEvent.inTransaction( - receipt.tx, - Pool, - 'ValidatorRegistered', - { - operator, - publicKey: validatorsDepositData[i].publicKey, - } - ); - expect( - await validators.isValidatorRegistered( - keccak256( - defaultAbiCoder.encode( - ['bytes'], - [validatorsDepositData[i].publicKey] - ) - ) - ) - ).to.equal(true); - await checkValidatorRegistered({ - transaction: receipt.tx, - pubKey: validatorsDepositData[i].publicKey, - withdrawalCredentials: validatorsDepositData[i].withdrawalCredentials, - signature: validatorsDepositData[i].signature, - validatorDepositAmount, - }); - } - - let _operator = await validators.getOperator(operator); - expect(_operator[1]).to.equal(true); - expect(await balance.current(pool.address)).to.bignumber.equal( - poolBalance.sub(validatorDepositAmount.mul(new BN(depositData.length))) - ); - }); - }); -}); diff --git a/test/pool/settings.test.js b/test/pool/settings.test.js deleted file mode 100644 index 5f3424e8..00000000 --- a/test/pool/settings.test.js +++ /dev/null @@ -1,204 +0,0 @@ -const { expect } = require('chai'); -const { - send, - ether, - expectRevert, - expectEvent, -} = require('@openzeppelin/test-helpers'); -const { - stopImpersonatingAccount, - impersonateAccount, - resetFork, - setActivatedValidators, - setupOracleAccounts, - registerValidators, -} = require('../utils'); -const { upgradeContracts } = require('../../deployments'); -const { contractSettings } = require('../../deployments/settings'); -const { - depositData, - depositDataMerkleRoot, -} = require('./depositDataMerkleRoot'); - -const Pool = artifacts.require('Pool'); -const Oracles = artifacts.require('Oracles'); -const PoolValidators = artifacts.require('PoolValidators'); -const RewardEthToken = artifacts.require('RewardEthToken'); -const iDepositContract = artifacts.require('IDepositContract'); - -contract('Pool (settings)', ([operator, anyone, ...otherAccounts]) => { - const admin = contractSettings.admin; - let pool, oracles, oracleAccounts, validatorsDepositRoot, contracts; - - after(async () => stopImpersonatingAccount(admin)); - - beforeEach(async () => { - await impersonateAccount(admin); - await send.ether(anyone, admin, ether('5')); - - contracts = await upgradeContracts(); - let validators = await PoolValidators.at(contracts.poolValidators); - await validators.addOperator( - operator, - depositDataMerkleRoot, - 'ipfs://QmSTP443zR6oKnYVRE23RARyuuzwhhaidUiSXyRTsw3pDs', - { - from: admin, - } - ); - await validators.commitOperator({ - from: operator, - }); - pool = await Pool.at(contracts.pool); - let depositContract = await iDepositContract.at( - await pool.validatorRegistration() - ); - validatorsDepositRoot = await depositContract.get_deposit_root(); - oracles = await Oracles.at(contracts.oracles); - rewardEthToken = await RewardEthToken.at(contracts.rewardEthToken); - oracleAccounts = await setupOracleAccounts({ - admin, - oracles, - accounts: otherAccounts, - }); - }); - - afterEach(async () => resetFork()); - - describe('min activating deposit', () => { - it('not admin fails to set min activating deposit', async () => { - await expectRevert( - pool.setMinActivatingDeposit(ether('10'), { - from: anyone, - }), - 'OwnablePausable: access denied' - ); - }); - - it('admin can set min activating deposit', async () => { - let minActivatingDeposit = ether('10'); - let receipt = await pool.setMinActivatingDeposit(minActivatingDeposit, { - from: admin, - }); - await expectEvent(receipt, 'MinActivatingDepositUpdated', { - minActivatingDeposit, - sender: admin, - }); - expect(await pool.minActivatingDeposit()).to.bignumber.equal( - minActivatingDeposit - ); - }); - }); - - describe('pending validators limit', () => { - it('not admin fails to set pending validators limit', async () => { - await expectRevert( - pool.setPendingValidatorsLimit('1000', { - from: anyone, - }), - 'OwnablePausable: access denied' - ); - }); - - it('admin can set pending validators limit', async () => { - let pendingValidatorsLimit = '1000'; - let receipt = await pool.setPendingValidatorsLimit( - pendingValidatorsLimit, - { - from: admin, - } - ); - await expectEvent(receipt, 'PendingValidatorsLimitUpdated', { - pendingValidatorsLimit, - sender: admin, - }); - expect(await pool.pendingValidatorsLimit()).to.bignumber.equal( - pendingValidatorsLimit - ); - }); - - it('fails to set invalid pending validators limit', async () => { - await expectRevert( - pool.setPendingValidatorsLimit(10000, { - from: admin, - }), - 'Pool: invalid limit' - ); - }); - }); - - describe('activated validators', () => { - it('not oracles contract or admin fails to set activated validators', async () => { - await expectRevert( - pool.setActivatedValidators('10', { - from: anyone, - }), - 'Pool: access denied' - ); - }); - - it('admin can override activated validators', async () => { - let activatedValidators = await pool.activatedValidators(); - activatedValidators = activatedValidators.add( - await pool.pendingValidators() - ); - - let receipt = await pool.setActivatedValidators(activatedValidators, { - from: admin, - }); - expectEvent(receipt, 'ActivatedValidatorsUpdated', { - activatedValidators, - }); - expect(await pool.activatedValidators()).to.bignumber.equal( - activatedValidators - ); - }); - - it('oracles contract can set activated validators', async () => { - await pool.stake({ - from: anyone, - value: ether('32'), - }); - await registerValidators({ - depositData: [ - { - operator, - withdrawalCredentials: depositData[0].withdrawalCredentials, - depositDataRoot: depositData[0].depositDataRoot, - publicKey: depositData[0].publicKey, - signature: depositData[0].signature, - }, - ], - merkleProofs: [depositData[0].merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot, - }); - - let activatedValidators = await pool.activatedValidators(); - activatedValidators = activatedValidators.add( - await pool.pendingValidators() - ); - - let receipt = await setActivatedValidators({ - pool, - rewardEthToken, - activatedValidators, - oracleAccounts, - oracles, - }); - await expectEvent.inTransaction( - receipt.tx, - Pool, - 'ActivatedValidatorsUpdated', - { - activatedValidators, - sender: oracles.address, - } - ); - expect(await pool.activatedValidators()).to.bignumber.equal( - activatedValidators - ); - }); - }); -}); diff --git a/test/pool/stake.test.js b/test/pool/stake.test.js deleted file mode 100644 index e1ddb748..00000000 --- a/test/pool/stake.test.js +++ /dev/null @@ -1,675 +0,0 @@ -const { expect } = require('chai'); -const { - ether, - balance, - send, - expectRevert, - expectEvent, - constants, - BN, -} = require('@openzeppelin/test-helpers'); -const { - stopImpersonatingAccount, - impersonateAccount, - resetFork, - getDepositAmount, - registerValidators, - setupOracleAccounts, -} = require('../utils'); -const { upgradeContracts } = require('../../deployments'); -const { contractSettings, contracts } = require('../../deployments/settings'); -const { checkStakedEthToken } = require('../utils'); -const { - depositData, - depositDataMerkleRoot, -} = require('./depositDataMerkleRoot'); - -const Pool = artifacts.require('Pool'); -const StakedEthToken = artifacts.require('StakedEthToken'); -const PoolValidators = artifacts.require('PoolValidators'); -const Oracles = artifacts.require('Oracles'); -const iDepositContract = artifacts.require('IDepositContract'); - -contract('Pool (stake)', (accounts) => { - const admin = contractSettings.admin; - let [sender1, sender2, sender3, operator, ...otherAccounts] = accounts; - let pool, - stakedEthToken, - validators, - oracles, - oracleAccounts, - totalSupply, - poolBalance, - activatedValidators, - pendingValidators, - depositContract, - validatorsDepositRoot; - - after(async () => stopImpersonatingAccount(admin)); - - beforeEach(async () => { - await impersonateAccount(admin); - await send.ether(sender3, admin, ether('5')); - let upgradedContracts = await upgradeContracts(); - - pool = await Pool.at(contracts.pool); - stakedEthToken = await StakedEthToken.at(contracts.stakedEthToken); - validators = await PoolValidators.at(upgradedContracts.poolValidators); - oracles = await Oracles.at(upgradedContracts.oracles); - oracleAccounts = await setupOracleAccounts({ - admin, - oracles, - accounts: otherAccounts, - }); - depositContract = await iDepositContract.at( - await pool.validatorRegistration() - ); - validatorsDepositRoot = await depositContract.get_deposit_root(); - await validators.addOperator( - operator, - depositDataMerkleRoot, - 'ipfs://QmSTP443zR6oKnYVRE23RARyuuzwhhaidUiSXyRTsw3pDs', - { - from: admin, - } - ); - await validators.commitOperator({ - from: operator, - }); - - totalSupply = await stakedEthToken.totalSupply(); - poolBalance = await balance.current(pool.address); - activatedValidators = await pool.activatedValidators(); - pendingValidators = await pool.pendingValidators(); - }); - - afterEach(async () => resetFork()); - - describe('stake', () => { - it('fails to stake with zero amount', async () => { - await expectRevert( - pool.stake({ from: sender1, value: ether('0') }), - 'Pool: invalid deposit amount' - ); - }); - - it('fails to stake with zero address', async () => { - await expectRevert( - pool.stakeOnBehalf(constants.ZERO_ADDRESS, { - from: sender1, - value: ether('0'), - }), - 'Pool: invalid recipient' - ); - }); - - it('fails to stake in paused pool', async () => { - await pool.pause({ from: admin }); - expect(await pool.paused()).equal(true); - - await expectRevert( - pool.stake({ - from: sender1, - value: ether('1'), - }), - 'Pausable: paused' - ); - }); - - it('mints tokens for users with deposit less than min activating', async () => { - let maxAmount = ether('0.01'); - await pool.setMinActivatingDeposit(maxAmount, { from: admin }); - // User 1 creates a deposit - let depositAmount1 = getDepositAmount({ - max: maxAmount, - }); - totalSupply = totalSupply.add(depositAmount1); - poolBalance = poolBalance.add(depositAmount1); - - await pool.stake({ - from: sender1, - value: depositAmount1, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: depositAmount1, - }); - - // User 2 creates a deposit - maxAmount = await pool.minActivatingDeposit(); - let depositAmount2 = getDepositAmount({ - max: maxAmount, - }); - totalSupply = totalSupply.add(depositAmount2); - poolBalance = poolBalance.add(depositAmount2); - - await pool.stake({ - from: sender2, - value: depositAmount2, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender2, - balance: depositAmount2, - }); - - // check contract balance - expect(await balance.current(pool.address)).to.be.bignumber.equal( - poolBalance - ); - }); - - it('places deposit of user to the activation queue with exceeded pending validators limit', async () => { - await pool.setPendingValidatorsLimit('1', { from: admin }); // 0.01 % - await pool.setMinActivatingDeposit(ether('0.01'), { from: admin }); - - // deposit more than 0.01 % - let depositAmount = ether('32').mul(new BN(2)); - poolBalance = poolBalance.add(depositAmount); - let validatorIndex = activatedValidators - .add(pendingValidators) - .add(poolBalance.div(ether('32'))); - - // check deposit amount placed in activation queue - let receipt = await pool.stake({ - from: sender1, - value: depositAmount, - }); - await expectEvent(receipt, 'ActivationScheduled', { - sender: sender1, - validatorIndex, - value: depositAmount, - }); - expect( - await pool.activations(sender1, validatorIndex) - ).to.bignumber.equal(depositAmount); - - // check contract balance - expect(await balance.current(pool.address)).to.be.bignumber.equal( - poolBalance - ); - expect(await stakedEthToken.totalSupply()).to.bignumber.equal( - totalSupply - ); - }); - - it('activates deposit of user immediately with not exceeded pending validators limit', async () => { - await pool.setPendingValidatorsLimit('1000', { from: admin }); // 10 % - await pool.setMinActivatingDeposit(ether('0.01'), { from: admin }); - - // deposit less than 10 % - let depositAmount = ether('32'); - poolBalance = poolBalance.add(depositAmount); - let validatorIndex = activatedValidators - .add(pendingValidators) - .add(new BN(1)); - totalSupply = totalSupply.add(depositAmount); - - // check deposit amount added immediately - await pool.stake({ - from: sender1, - value: depositAmount, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: depositAmount, - }); - expect( - await pool.activations(sender1, validatorIndex) - ).to.bignumber.equal(new BN(0)); - - // check contract balance - expect(await balance.current(pool.address)).to.be.bignumber.equal( - poolBalance - ); - }); - - it('can stake to different recipient address', async () => { - let amount = ether('1'); - totalSupply = totalSupply.add(amount); - - let receipt = await pool.stakeOnBehalf(sender2, { - from: sender1, - value: amount, - }); - await expectEvent.inTransaction(receipt.tx, StakedEthToken, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: sender2, - value: amount, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender2, - balance: amount, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: new BN(0), - }); - }); - - it('can stake without recipient address', async () => { - let amount = ether('1'); - totalSupply = totalSupply.add(amount); - - let receipt = await pool.stake({ - from: sender1, - value: amount, - }); - await expectEvent.inTransaction(receipt.tx, StakedEthToken, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: sender1, - value: amount, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: amount, - }); - }); - - describe('staking with partner', () => { - const partner = otherAccounts[0]; - - it('can stake with partner', async () => { - let amount = ether('1'); - totalSupply = totalSupply.add(amount); - - let receipt = await pool.stakeWithPartner(partner, { - from: sender1, - value: amount, - }); - await expectEvent(receipt, 'StakedWithPartner', { - partner, - amount, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: amount, - }); - }); - - it('can stake with partner to different recipient address', async () => { - let amount = ether('1'); - totalSupply = totalSupply.add(amount); - - let receipt = await pool.stakeWithPartnerOnBehalf(partner, sender2, { - from: sender1, - value: amount, - }); - await expectEvent(receipt, 'StakedWithPartner', { - partner, - amount, - }); - await expectEvent.inTransaction( - receipt.tx, - StakedEthToken, - 'Transfer', - { - from: constants.ZERO_ADDRESS, - to: sender2, - value: amount, - } - ); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender2, - balance: amount, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: new BN(0), - }); - }); - }); - - describe('staking with referrer', () => { - const referrer = otherAccounts[0]; - - it('can stake with referrer', async () => { - let amount = ether('1'); - totalSupply = totalSupply.add(amount); - - let receipt = await pool.stakeWithReferrer(referrer, { - from: sender1, - value: amount, - }); - await expectEvent(receipt, 'StakedWithReferrer', { - referrer, - amount, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: amount, - }); - }); - - it('can stake with referrer to different recipient address', async () => { - let amount = ether('1'); - totalSupply = totalSupply.add(amount); - - let receipt = await pool.stakeWithReferrerOnBehalf(referrer, sender2, { - from: sender1, - value: amount, - }); - await expectEvent(receipt, 'StakedWithReferrer', { - referrer, - amount, - }); - await expectEvent.inTransaction( - receipt.tx, - StakedEthToken, - 'Transfer', - { - from: constants.ZERO_ADDRESS, - to: sender2, - value: amount, - } - ); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender2, - balance: amount, - }); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: new BN(0), - }); - }); - }); - }); - - describe('activating', () => { - let validatorIndex, depositAmount; - - beforeEach(async () => { - await pool.setPendingValidatorsLimit('1', { from: admin }); // 0.01 % - await pool.setMinActivatingDeposit(ether('0.01'), { from: admin }); - - depositAmount = ether('32'); - await pool.stake({ - from: sender1, - value: depositAmount, - }); - poolBalance = await balance.current(pool.address); - validatorIndex = activatedValidators - .add(pendingValidators) - .add(poolBalance.div(ether('32'))); - - for ( - let i = 0; - i < validatorIndex.sub(activatedValidators).sub(pendingValidators); - i++ - ) { - validatorsDepositRoot = await depositContract.get_deposit_root(); - await registerValidators({ - depositData: [ - { - operator, - withdrawalCredentials: depositData[i].withdrawalCredentials, - depositDataRoot: depositData[i].depositDataRoot, - publicKey: depositData[i].publicKey, - signature: depositData[i].signature, - }, - ], - merkleProofs: [depositData[i].merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot, - }); - } - }); - - it('fails to activate with invalid validator index', async () => { - await expectRevert( - pool.activate(sender1, validatorIndex, { - from: sender1, - }), - 'Pool: validator is not active yet' - ); - }); - - it('fails to activate in paused pool', async () => { - await pool.pause({ from: admin }); - expect(await pool.paused()).equal(true); - - await expectRevert( - pool.activate(sender1, validatorIndex, { - from: sender1, - }), - 'Pausable: paused' - ); - }); - - it('fails to activate not existing deposit', async () => { - await pool.setActivatedValidators(validatorIndex, { - from: admin, - }); - await expectRevert( - pool.activate(sender2, validatorIndex, { - from: sender1, - }), - 'Pool: invalid validator index' - ); - }); - - it('fails to activate deposit amount twice', async () => { - await pool.setActivatedValidators(validatorIndex, { - from: admin, - }); - await pool.activate(sender1, validatorIndex, { - from: sender1, - }); - - await expectRevert( - pool.activate(sender1, validatorIndex, { - from: sender1, - }), - 'Pool: invalid validator index' - ); - }); - - it('activates deposit amount', async () => { - await pool.setActivatedValidators(validatorIndex, { - from: admin, - }); - expect(await pool.canActivate(validatorIndex)).to.equal(true); - let receipt = await pool.activate(sender1, validatorIndex, { - from: sender1, - }); - await expectEvent(receipt, 'Activated', { - account: sender1, - validatorIndex, - value: depositAmount, - sender: sender1, - }); - totalSupply = totalSupply.add(depositAmount); - - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: depositAmount, - }); - }); - }); - - describe('activating multiple', () => { - let validatorIndex1, validatorIndex2, depositAmount; - - beforeEach(async () => { - await pool.setPendingValidatorsLimit('1', { from: admin }); // 0.01 % - await pool.setMinActivatingDeposit(ether('0.01'), { from: admin }); - - depositAmount = ether('32'); - await pool.stake({ - from: sender3, - value: depositAmount, - }); - poolBalance = poolBalance.add(depositAmount); - validatorIndex1 = activatedValidators - .add(pendingValidators) - .add(poolBalance.div(ether('32'))); - - await pool.stake({ - from: sender3, - value: depositAmount, - }); - poolBalance = poolBalance.add(depositAmount); - validatorIndex2 = activatedValidators - .add(pendingValidators) - .add(poolBalance.div(ether('32'))); - - for ( - let i = 0; - i < validatorIndex2.sub(activatedValidators).sub(pendingValidators); - i++ - ) { - validatorsDepositRoot = await depositContract.get_deposit_root(); - await registerValidators({ - depositData: [ - { - operator, - withdrawalCredentials: depositData[i].withdrawalCredentials, - depositDataRoot: depositData[i].depositDataRoot, - publicKey: depositData[i].publicKey, - signature: depositData[i].signature, - }, - ], - merkleProofs: [depositData[i].merkleProof], - oracles, - oracleAccounts, - validatorsDepositRoot, - }); - } - }); - - it('fails to activate with invalid validator indexes', async () => { - await expectRevert( - pool.activateMultiple( - sender3, - [validatorIndex1.add(new BN(2)), validatorIndex2.add(new BN(3))], - { - from: sender3, - } - ), - 'Pool: validator is not active yet' - ); - }); - - it('fails to activate in paused pool', async () => { - await pool.pause({ from: admin }); - expect(await pool.paused()).equal(true); - - await expectRevert( - pool.activateMultiple(sender3, [validatorIndex1, validatorIndex2], { - from: sender3, - }), - 'Pausable: paused' - ); - }); - - it('fails to activate not existing deposit', async () => { - await pool.setActivatedValidators(validatorIndex2, { - from: admin, - }); - await expectRevert( - pool.activateMultiple(sender2, [validatorIndex1, validatorIndex2], { - from: sender3, - }), - 'Pool: invalid validator index' - ); - }); - - it('fails to activate multiple deposit amounts twice', async () => { - await pool.setActivatedValidators(validatorIndex2, { - from: admin, - }); - await pool.activateMultiple(sender3, [validatorIndex1, validatorIndex2], { - from: sender3, - }); - - await expectRevert( - pool.activateMultiple(sender3, [validatorIndex1, validatorIndex2], { - from: sender3, - }), - 'Pool: invalid validator index' - ); - }); - - it('activates multiple deposit amounts', async () => { - await pool.setActivatedValidators(validatorIndex2, { - from: admin, - }); - expect(await pool.canActivate(validatorIndex1)).to.equal(true); - expect(await pool.canActivate(validatorIndex2)).to.equal(true); - let receipt = await pool.activateMultiple( - sender3, - [validatorIndex1, validatorIndex2], - { - from: sender3, - } - ); - totalSupply = totalSupply.add(depositAmount.mul(new BN(2))); - - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender3, - balance: depositAmount.mul(new BN(2)), - }); - - await expectEvent.inTransaction(receipt.tx, Pool, 'Activated', { - account: sender3, - validatorIndex: validatorIndex1, - value: depositAmount, - sender: sender3, - }); - await expectEvent.inTransaction(receipt.tx, Pool, 'Activated', { - account: sender3, - validatorIndex: validatorIndex2, - value: depositAmount, - sender: sender3, - }); - }); - }); - - it('only PoolValidators contract can register new validators', async () => { - const { publicKey, signature, withdrawalCredentials, depositDataRoot } = - depositData[0]; - await expectRevert( - pool.registerValidator( - { - operator, - withdrawalCredentials, - depositDataRoot, - publicKey, - signature, - }, - { - from: sender1, - } - ), - 'Pool: access denied' - ); - }); -}); diff --git a/test/tokens/RewardEthToken.test.js b/test/tokens/RewardEthToken.test.js index b24b48a1..938a0ad4 100644 --- a/test/tokens/RewardEthToken.test.js +++ b/test/tokens/RewardEthToken.test.js @@ -1,4 +1,3 @@ -const { hexlify, keccak256, defaultAbiCoder } = require('ethers/lib/utils'); const { expect } = require('chai'); const { expectRevert, @@ -7,9 +6,11 @@ const { ether, constants, send, - balance, } = require('@openzeppelin/test-helpers'); -const { upgradeContracts } = require('../../deployments'); +const { + upgradeContracts, + upgradeRewardEthToken, +} = require('../../deployments'); const { contractSettings, contracts } = require('../../deployments/settings'); const { stopImpersonatingAccount, @@ -17,24 +18,22 @@ const { resetFork, checkRewardEthToken, setTotalRewards, - setupOracleAccounts, + addStakedEthToken, + addRewardEthToken, + checkStakedEthToken, } = require('../utils'); +const { ethers } = require('hardhat'); const StakedEthToken = artifacts.require('StakedEthToken'); const RewardEthToken = artifacts.require('RewardEthToken'); -const Pool = artifacts.require('Pool'); const Oracles = artifacts.require('Oracles'); const MulticallMock = artifacts.require('MulticallMock'); +const VaultMock = artifacts.require('VaultMock'); const protocolFee = new BN(1000); -contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { +contract('RewardEthToken', ([sender, merkleDistributor, vault, ...others]) => { const admin = contractSettings.admin; - let stakedEthToken, - rewardEthToken, - totalSupply, - pool, - oracles, - oracleAccounts; + let stakedEthToken, rewardEthToken, totalSupply, oracles; after(async () => stopImpersonatingAccount(admin)); @@ -42,14 +41,12 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { await impersonateAccount(admin); await send.ether(sender, admin, ether('5')); - let contracts = await upgradeContracts(); + let contracts = await upgradeContracts(vault); stakedEthToken = await StakedEthToken.at(contracts.stakedEthToken); rewardEthToken = await RewardEthToken.at(contracts.rewardEthToken); - pool = await Pool.at(contracts.pool); oracles = await Oracles.at(contracts.oracles); - oracleAccounts = await setupOracleAccounts({ oracles, admin, accounts }); totalSupply = await rewardEthToken.totalSupply(); await rewardEthToken.setProtocolFee(protocolFee, { from: admin }); }); @@ -128,12 +125,6 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { }); describe('updateTotalRewards', () => { - let feesEscrowBalance; - - beforeEach(async () => { - feesEscrowBalance = await balance.current(contracts.feesEscrow); - }); - it('anyone cannot update rewards', async () => { await expectRevert( rewardEthToken.updateTotalRewards(ether('10'), { @@ -149,17 +140,14 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { }); }); - it('oracles can update rewards', async () => { + it('vault can update rewards', async () => { let prevTotalRewards = await rewardEthToken.totalRewards(); let newTotalRewards = prevTotalRewards.add(ether('10')); let receipt = await setTotalRewards({ rewardEthToken, - oracles, - pool, + vault, totalRewards: newTotalRewards, - oracleAccounts, }); - newTotalRewards = newTotalRewards.add(feesEscrowBalance); await expectEvent.inTransaction( receipt.tx, RewardEthToken, @@ -171,21 +159,6 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { ); }); - it('anyone cannot update rewards', async () => { - await expectRevert( - rewardEthToken.updateTotalRewards(ether('10'), { - from: sender, - }), - 'RewardEthToken: access denied' - ); - await checkRewardEthToken({ - rewardEthToken, - totalSupply, - account: sender, - balance: new BN(0), - }); - }); - it('assigns protocol fee to distributor', async () => { await rewardEthToken.setProtocolFeeRecipient(constants.ZERO_ADDRESS, { from: admin, @@ -196,13 +169,9 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { let newTotalRewards = prevTotalRewards.add(periodReward); let receipt = await setTotalRewards({ rewardEthToken, - oracles, - pool, + vault, totalRewards: newTotalRewards, - oracleAccounts, }); - periodReward = periodReward.add(feesEscrowBalance); - newTotalRewards = newTotalRewards.add(feesEscrowBalance); await expectEvent.inTransaction( receipt.tx, RewardEthToken, @@ -216,37 +185,108 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { } ); }); + + it('accumulates penalty', async () => { + let penalty = ether('10'); + let totalRewards = await rewardEthToken.totalRewards(); + let totalPenalty = await rewardEthToken.totalPenalty(); + + let receipt = await setTotalRewards({ + rewardEthToken, + vault, + totalRewards: totalRewards.sub(penalty), + }); + await expectEvent.inTransaction( + receipt.tx, + RewardEthToken, + 'RewardsUpdated', + { + periodRewards: '0', + totalRewards: totalRewards, + protocolReward: '0', + } + ); + totalPenalty = totalPenalty.add(ether('10')); + expect(await rewardEthToken.totalPenalty()).to.bignumber.equal( + totalPenalty + ); + + // reduces penalty partially + let periodReward = ether('5'); + totalPenalty = totalPenalty.sub(periodReward); + receipt = await setTotalRewards({ + rewardEthToken, + vault, + totalRewards: totalRewards.add(periodReward), + }); + await expectEvent.inTransaction( + receipt.tx, + RewardEthToken, + 'RewardsUpdated', + { + periodRewards: '0', + totalRewards: totalRewards, + protocolReward: '0', + } + ); + expect(await rewardEthToken.totalPenalty()).to.bignumber.equal( + totalPenalty + ); + + // reduces penalty completely + periodReward = ether('1'); + receipt = await setTotalRewards({ + rewardEthToken, + vault, + totalRewards: totalRewards.add(periodReward).add(totalPenalty), + }); + totalPenalty = new BN(0); + totalRewards = totalRewards.add(periodReward); + await expectEvent.inTransaction( + receipt.tx, + RewardEthToken, + 'RewardsUpdated', + { + periodRewards: periodReward, + totalRewards: totalRewards, + protocolReward: periodReward + .mul(await rewardEthToken.protocolFee()) + .div(new BN(10000)), + } + ); + expect(await rewardEthToken.totalPenalty()).to.bignumber.equal( + totalPenalty + ); + }); + + it('penalty cannot exceed total assets', async () => { + let totalAssets = await rewardEthToken.totalAssets(); + + await expectRevert( + rewardEthToken.updateTotalRewards(totalAssets.add(new BN(1)).neg(), { + from: vault, + }), + 'RewardEthToken: invalid penalty amount' + ); + }); }); describe('transfer', () => { const stakedAmount1 = ether('4'); const stakedAmount2 = ether('5'); - const [sender1, sender2] = accounts; + const [sender1, sender2] = others; let rewardAmount1, rewardAmount2; beforeEach(async () => { - await pool.setMinActivatingDeposit(stakedAmount2.add(ether('1')), { - from: admin, - }); - await pool.stake({ - from: sender1, - value: stakedAmount1, - }); - await pool.stake({ - from: sender2, - value: stakedAmount2, - }); + await addStakedEthToken(stakedEthToken, sender1, stakedAmount1); + await addStakedEthToken(stakedEthToken, sender2, stakedAmount2); - let feesEscrowBalance = await balance.current(contracts.feesEscrow); totalSupply = (await rewardEthToken.totalSupply()).add(ether('10')); await setTotalRewards({ totalRewards: totalSupply, rewardEthToken, - pool, - oracles, - oracleAccounts, + vault, }); - totalSupply = totalSupply.add(feesEscrowBalance); rewardAmount1 = await rewardEthToken.balanceOf(sender1); rewardAmount2 = await rewardEthToken.balanceOf(sender2); @@ -379,37 +419,19 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { rewardEthToken.address, merkleDistributor ); - await oracles.addOracle(multicallMock.address, { - from: admin, - }); + + const signer = await ethers.provider.getSigner(contractSettings.admin); + await upgradeRewardEthToken(signer, multicallMock.address); await rewardEthToken.approve(multicallMock.address, rewardAmount1, { from: sender1, }); - let currentNonce = await oracles.currentRewardsNonce(); - let totalRewards = (await rewardEthToken.totalRewards()).add(ether('10')); - let activatedValidators = await pool.activatedValidators(); - let signatures = []; - let encoded = defaultAbiCoder.encode( - ['uint256', 'uint256', 'uint256'], - [ - currentNonce.toString(), - activatedValidators.toString(), - totalRewards.toString(), - ] - ); - let candidateId = hexlify(keccak256(encoded)); - for (const oracleAccount of oracleAccounts) { - signatures.push(await web3.eth.sign(candidateId, oracleAccount)); - } - + const rewardsDelta = ether('10'); await expectRevert( multicallMock.updateTotalRewardsAndTransferRewards( - totalRewards, - activatedValidators, + rewardsDelta, sender2, - signatures, { from: sender1, } @@ -426,36 +448,18 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { rewardEthToken.address, merkleDistributor ); - await oracles.addOracle(multicallMock.address, { - from: admin, - }); + + const signer = await ethers.provider.getSigner(contractSettings.admin); + await upgradeRewardEthToken(signer, multicallMock.address); await rewardEthToken.approve(multicallMock.address, rewardAmount1, { from: sender1, }); - let currentNonce = await oracles.currentRewardsNonce(); - let totalRewards = (await rewardEthToken.totalRewards()).add(ether('10')); - let activatedValidators = await pool.activatedValidators(); - let signatures = []; - let encoded = defaultAbiCoder.encode( - ['uint256', 'uint256', 'uint256'], - [ - currentNonce.toString(), - activatedValidators.toString(), - totalRewards.toString(), - ] - ); - let candidateId = hexlify(keccak256(encoded)); - for (const oracleAccount of oracleAccounts) { - signatures.push(await web3.eth.sign(candidateId, oracleAccount)); - } - + const rewardsDelta = ether('10'); let receipt = await multicallMock.transferRewardsAndUpdateTotalRewards( - totalRewards, - activatedValidators, + rewardsDelta, sender2, - signatures, { from: sender1, } @@ -468,4 +472,186 @@ contract('RewardEthToken', ([sender, merkleDistributor, ...accounts]) => { }); }); }); + + describe('migrate', () => { + const stakedAmount = ether('1'); + const rewardAmount = ether('1'); + let vaultMock; + + beforeEach(async () => { + vaultMock = await VaultMock.new(rewardEthToken.address); + const signer = await ethers.provider.getSigner(contractSettings.admin); + await upgradeRewardEthToken(signer, vaultMock.address); + await addStakedEthToken(stakedEthToken, sender, stakedAmount); + await addRewardEthToken(rewardEthToken, sender, rewardAmount); + }); + + it('cannot migrate to zero address receiver', async () => { + await expectRevert( + rewardEthToken.migrate( + constants.ZERO_ADDRESS, + stakedAmount, + rewardAmount, + { + from: sender, + } + ), + 'RewardEthToken: invalid receiver' + ); + }); + + it('cannot migrate after total rewards update in the same block', async () => { + const multicallMock = await MulticallMock.new( + oracles.address, + contracts.stakedEthToken, + contracts.rewardEthToken, + merkleDistributor + ); + await rewardEthToken.transfer(multicallMock.address, ether('1'), { + from: sender, + }); + await stakedEthToken.transfer(multicallMock.address, ether('1'), { + from: sender, + }); + const signer = await ethers.provider.getSigner(contractSettings.admin); + await upgradeRewardEthToken(signer, multicallMock.address); + + const rewardsDelta = ether('10'); + await expectRevert( + multicallMock.updateTotalRewardsAndMigrate(rewardsDelta, { + from: sender, + }), + 'RewardEthToken: cannot migrate during rewards update' + ); + }); + + it('deducts penalty from user assets', async () => { + let penalty = ether('-10'); + await vaultMock.updateTotalRewards(penalty, { + from: vault, + }); + let totalPenalty = await rewardEthToken.totalPenalty(); + const totalRewards = await rewardEthToken.totalSupply(); + const totalStaked = await stakedEthToken.totalSupply(); + + await rewardEthToken.migrate(sender, stakedAmount, rewardAmount, { + from: sender, + }); + + expect(await rewardEthToken.totalPenalty()).to.be.bignumber.lessThan( + totalPenalty + ); + expect(await vaultMock.migratedAssets()).to.be.bignumber.lessThan( + stakedAmount.add(rewardAmount) + ); + + await checkStakedEthToken({ + stakedEthToken, + totalSupply: totalStaked.sub(stakedAmount), + account: sender, + balance: new BN(0), + }); + + await checkRewardEthToken({ + rewardEthToken, + totalSupply: totalRewards.sub(rewardAmount), + account: sender, + balance: new BN(0), + }); + }); + + it('cannot migrate zero assets', async () => { + await expectRevert( + rewardEthToken.migrate(sender, new BN(0), new BN(0), { + from: sender, + }), + 'RewardEthToken: zero assets' + ); + }); + + it('cannot migrate rETH2 larger than balance', async () => { + await expectRevert( + rewardEthToken.migrate( + sender, + stakedAmount.add(new BN(1)), + rewardAmount, + { + from: sender, + } + ), + 'SafeMath: subtraction overflow' + ); + }); + + it('cannot migrate sETH2 larger than balance', async () => { + await expectRevert( + rewardEthToken.migrate( + sender, + stakedAmount, + rewardAmount.add(new BN(1)), + { + from: sender, + } + ), + 'SafeMath: subtraction overflow' + ); + }); + + it('can migrate sETH2 and rETH2', async () => { + let totalRewards = await rewardEthToken.totalSupply(); + let totalStaked = await stakedEthToken.totalSupply(); + let receipt = await rewardEthToken.migrate( + sender, + stakedAmount, + rewardAmount, + { + from: sender, + } + ); + const assets = stakedAmount.add(rewardAmount); + totalRewards = totalRewards.sub(rewardAmount); + totalStaked = totalStaked.sub(stakedAmount); + + await expectEvent.inTransaction(receipt.tx, VaultMock, 'Migrated', { + receiver: sender, + assets, + }); + + await expectEvent.inTransaction(receipt.tx, RewardEthToken, 'Transfer', { + from: sender, + to: constants.ZERO_ADDRESS, + value: rewardAmount, + }); + + await expectEvent.inTransaction(receipt.tx, StakedEthToken, 'Transfer', { + from: sender, + to: constants.ZERO_ADDRESS, + value: stakedAmount, + }); + + await checkStakedEthToken({ + stakedEthToken, + totalSupply: totalStaked, + account: sender, + balance: new BN(0), + }); + + await checkRewardEthToken({ + rewardEthToken, + totalSupply: totalRewards, + account: sender, + balance: new BN(0), + }); + + expect(await rewardEthToken.totalSupply()).to.be.bignumber.equal( + totalRewards + ); + expect(await stakedEthToken.totalSupply()).to.be.bignumber.equal( + totalStaked + ); + expect(await vaultMock.migratedAssets()).to.be.bignumber.equal( + stakedAmount.add(rewardAmount) + ); + }); + }); }); diff --git a/test/tokens/StakedEthToken.test.js b/test/tokens/StakedEthToken.test.js index d1e2faec..509afe4b 100644 --- a/test/tokens/StakedEthToken.test.js +++ b/test/tokens/StakedEthToken.test.js @@ -1,4 +1,3 @@ -const { hexlify, keccak256, defaultAbiCoder } = require('ethers/lib/utils'); const { expect } = require('chai'); const { expectRevert, @@ -13,64 +12,37 @@ const { stopImpersonatingAccount, resetFork, checkStakedEthToken, - setupOracleAccounts, setTotalRewards, + addStakedEthToken, } = require('../utils'); -const { upgradeContracts } = require('../../deployments'); +const { + upgradeContracts, + upgradeRewardEthToken, +} = require('../../deployments'); const { contractSettings, contracts } = require('../../deployments/settings'); +const { ethers } = require('hardhat'); const StakedEthToken = artifacts.require('StakedEthToken'); const RewardEthToken = artifacts.require('RewardEthToken'); -const Pool = artifacts.require('Pool'); const Oracles = artifacts.require('Oracles'); const MulticallMock = artifacts.require('MulticallMock'); contract('StakedEthToken', (accounts) => { const admin = contractSettings.admin; - const [merkleDistributor, sender1, sender2, ...otherAccounts] = accounts; - let stakedEthToken, - rewardEthToken, - pool, - totalSupply, - oracles, - oracleAccounts, - activatedValidators, - totalRewards, - signatures; + const [merkleDistributor, sender1, sender2, vault] = accounts; + let stakedEthToken, rewardEthToken, oracles, totalSupply, totalRewards; beforeEach(async () => { await impersonateAccount(admin); await send.ether(sender1, admin, ether('5')); - let upgradedContracts = await upgradeContracts(); + await upgradeContracts(vault); stakedEthToken = await StakedEthToken.at(contracts.stakedEthToken); - pool = await Pool.at(contracts.pool); - oracles = await Oracles.at(upgradedContracts.oracles); - oracleAccounts = await setupOracleAccounts({ - oracles, - admin, - accounts: otherAccounts, - }); + oracles = await Oracles.at(contracts.oracles); rewardEthToken = await RewardEthToken.at(contracts.rewardEthToken); - totalRewards = (await rewardEthToken.totalRewards()).add(ether('10')); - let currentNonce = await oracles.currentRewardsNonce(); - activatedValidators = await pool.activatedValidators(); - let encoded = defaultAbiCoder.encode( - ['uint256', 'uint256', 'uint256'], - [ - currentNonce.toString(), - activatedValidators.toString(), - totalRewards.toString(), - ] - ); - signatures = []; - let candidateId = hexlify(keccak256(encoded)); - for (const oracleAccount of oracleAccounts) { - signatures.push(await web3.eth.sign(candidateId, oracleAccount)); - } - + totalRewards = await rewardEthToken.totalRewards(); totalSupply = await stakedEthToken.totalSupply(); }); @@ -78,55 +50,12 @@ contract('StakedEthToken', (accounts) => { afterEach(async () => resetFork()); - describe('mint', () => { - it('anyone cannot mint sETH2 tokens', async () => { - await expectRevert( - stakedEthToken.mint(sender1, ether('10'), { - from: sender1, - }), - 'StakedEthToken: access denied' - ); - await checkStakedEthToken({ - stakedEthToken, - totalSupply, - account: sender1, - balance: new BN(0), - }); - }); - - it('updates distributor principal when deposited by account with disabled rewards', async () => { - // disable rewards - let prevPrincipal = await stakedEthToken.distributorPrincipal(); - await stakedEthToken.toggleRewards(sender1, true, { from: admin }); - let amount = ether('10'); - let receipt = await pool.stake({ - from: sender1, - value: amount, - }); - await expectEvent.inTransaction(receipt.tx, StakedEthToken, 'Transfer', { - from: constants.ZERO_ADDRESS, - to: sender1, - value: amount, - }); - expect(await stakedEthToken.distributorPrincipal()).to.bignumber.equal( - prevPrincipal.add(amount) - ); - }); - }); - describe('transfer', () => { let value = ether('10'); let distributorPrincipal; beforeEach(async () => { - await pool.setMinActivatingDeposit(value.add(ether('1')), { - from: admin, - }); - await pool.stake({ - from: sender1, - value, - }); - totalSupply = totalSupply.add(value); + await addStakedEthToken(stakedEthToken, sender1, value); distributorPrincipal = await stakedEthToken.distributorPrincipal(); }); @@ -248,9 +177,7 @@ contract('StakedEthToken', (accounts) => { await setTotalRewards({ totalRewards, rewardEthToken, - pool, - oracles, - oracleAccounts, + vault, }); let rewardAmount = await rewardEthToken.balanceOf(sender1); @@ -382,9 +309,9 @@ contract('StakedEthToken', (accounts) => { contracts.rewardEthToken, merkleDistributor ); - await oracles.addOracle(multicallMock.address, { - from: admin, - }); + + const signer = await ethers.provider.getSigner(contractSettings.admin); + await upgradeRewardEthToken(signer, multicallMock.address); await stakedEthToken.approve(multicallMock.address, value, { from: sender1, @@ -393,9 +320,7 @@ contract('StakedEthToken', (accounts) => { await expectRevert( multicallMock.updateTotalRewardsAndTransferStakedEth( totalRewards, - activatedValidators, sender2, - signatures, { from: sender1, } @@ -412,9 +337,9 @@ contract('StakedEthToken', (accounts) => { contracts.rewardEthToken, merkleDistributor ); - await oracles.addOracle(multicallMock.address, { - from: admin, - }); + + const signer = await ethers.provider.getSigner(contractSettings.admin); + await upgradeRewardEthToken(signer, multicallMock.address); await stakedEthToken.approve(multicallMock.address, value, { from: sender1, @@ -422,9 +347,7 @@ contract('StakedEthToken', (accounts) => { let receipt = await multicallMock.transferStakedEthAndUpdateTotalRewards( totalRewards, - activatedValidators, sender2, - signatures, { from: sender1, } @@ -436,5 +359,21 @@ contract('StakedEthToken', (accounts) => { value, }); }); + + it('cannot burn from not rewardEthToken', async () => { + await expectRevert( + stakedEthToken.burn(sender1, value, { + from: sender1, + }), + 'StakedEthToken: access denied' + ); + + await checkStakedEthToken({ + stakedEthToken, + totalSupply, + account: sender1, + balance: value, + }); + }); }); }); diff --git a/test/tokens/toggleRewards.test.js b/test/tokens/toggleRewards.test.js index 2fabdeb7..5783e8ba 100644 --- a/test/tokens/toggleRewards.test.js +++ b/test/tokens/toggleRewards.test.js @@ -10,27 +10,19 @@ const { impersonateAccount, stopImpersonatingAccount, resetFork, - setupOracleAccounts, setTotalRewards, + addStakedEthToken, } = require('../utils'); const { contractSettings, contracts } = require('../../deployments/settings'); const { upgradeContracts } = require('../../deployments'); const RewardEthToken = artifacts.require('RewardEthToken'); -const Oracles = artifacts.require('Oracles'); -const Pool = artifacts.require('Pool'); const StakedEthToken = artifacts.require('StakedEthToken'); contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { let admin = contractSettings.admin; - let oracles, - rewardEthToken, - stakedEthToken, - distributorReward, - pool, - oracleAccounts, - distributorPrincipal; - let [account, anyone] = accounts; + let rewardEthToken, stakedEthToken, distributorReward, distributorPrincipal; + let [account, anyone, vault] = accounts; after(async () => stopImpersonatingAccount(admin)); @@ -38,12 +30,9 @@ contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { await impersonateAccount(admin); await send.ether(anyone, admin, ether('5')); - let upgradedContracts = await upgradeContracts(); - oracles = await Oracles.at(upgradedContracts.oracles); - pool = await Pool.at(contracts.pool); + await upgradeContracts(vault); rewardEthToken = await RewardEthToken.at(contracts.rewardEthToken); stakedEthToken = await StakedEthToken.at(contracts.stakedEthToken); - oracleAccounts = await setupOracleAccounts({ oracles, admin, accounts }); distributorPrincipal = await stakedEthToken.distributorPrincipal(); distributorReward = await rewardEthToken.balanceOf(constants.ZERO_ADDRESS); }); @@ -82,10 +71,7 @@ contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { let deposit = ether('5'); // mint sETH2 for disabled account - await pool.stake({ - from: account, - value: deposit, - }); + await addStakedEthToken(stakedEthToken, account, deposit); let receipt = await stakedEthToken.toggleRewards(account, true, { from: admin, @@ -127,10 +113,7 @@ contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { let deposit = ether('5'); // mint sETH2 for disabled account - await pool.stake({ - from: account, - value: deposit, - }); + await addStakedEthToken(stakedEthToken, account, deposit); expect(await stakedEthToken.balanceOf(account)).to.be.bignumber.equal( deposit ); @@ -147,10 +130,7 @@ contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { ).to.be.bignumber.equal(distributorReward); // mint sETH2 for normal account - await pool.stake({ - from: anyone, - value: ether('5'), - }); + await addStakedEthToken(stakedEthToken, anyone, deposit); expect(await stakedEthToken.balanceOf(anyone)).to.be.bignumber.equal( deposit ); @@ -162,10 +142,8 @@ contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { let totalRewards = (await rewardEthToken.totalRewards()).add(ether('10')); await setTotalRewards({ rewardEthToken, - oracles, - oracleAccounts, - pool, totalRewards, + vault, }); // arrived reward @@ -210,10 +188,7 @@ contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { it('toggling rewards does not affect current rewards balance', async () => { // mint sETH2 for disabled account let deposit = ether('5'); - await pool.stake({ - from: account, - value: deposit, - }); + await addStakedEthToken(stakedEthToken, account, deposit); // manual checkpoints update await rewardEthToken.updateRewardCheckpoint(account); @@ -226,10 +201,8 @@ contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { let totalRewards = (await rewardEthToken.totalRewards()).add(ether('10')); await setTotalRewards({ rewardEthToken, - oracles, - oracleAccounts, - pool, totalRewards, + vault, }); // manual checkpoints update @@ -291,10 +264,8 @@ contract('StakedEthToken (toggle rewards)', ([_, ...accounts]) => { totalRewards = totalRewards.add(ether('10')); await setTotalRewards({ rewardEthToken, - oracles, - oracleAccounts, - pool, totalRewards, + vault, }); // manual checkpoints update diff --git a/test/utils.js b/test/utils.js index 7623899e..62423f76 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,48 +1,7 @@ const { expect } = require('chai'); const hre = require('hardhat'); const { hexlify, keccak256, defaultAbiCoder } = require('ethers/lib/utils'); -const { - BN, - ether, - expectEvent, - balance, -} = require('@openzeppelin/test-helpers'); -const { contracts } = require('../deployments/settings'); - -const iDepositContract = artifacts.require('IDepositContract'); - -function getDepositAmount({ min = new BN('1'), max = ether('1000') } = {}) { - return ether(Math.random().toFixed(8)) - .mul(max.sub(min)) - .div(ether('1')) - .add(min); -} - -async function checkValidatorRegistered({ - transaction, - pubKey, - signature, - withdrawalCredentials, - validatorDepositAmount = ether('32'), -}) { - // Check VRC record created - await expectEvent.inTransaction( - transaction, - iDepositContract, - 'DepositEvent', - { - pubkey: pubKey, - withdrawal_credentials: withdrawalCredentials, - amount: web3.utils.bytesToHex( - new BN(web3.utils.fromWei(validatorDepositAmount, 'gwei')).toArray( - 'le', - 8 - ) - ), - signature: signature, - } - ); -} +const { BN } = require('@openzeppelin/test-helpers'); async function checkStakedEthToken({ stakedEthToken, @@ -128,47 +87,26 @@ async function setActivatedValidators({ return receipt; } -async function setTotalRewards({ - rewardEthToken, - oracles, - oracleAccounts, - pool, - totalRewards, -}) { - if ((await rewardEthToken.totalSupply()).eq(totalRewards)) { - return; - } - // calculate candidate ID - let activatedValidators = await pool.activatedValidators(); - let nonce = await oracles.currentRewardsNonce(); - let encoded = defaultAbiCoder.encode( - ['uint256', 'uint256', 'uint256'], - [nonce.toString(), activatedValidators.toString(), totalRewards.toString()] - ); - let candidateId = hexlify(keccak256(encoded)); - - // prepare signatures - let signatures = []; - for (let i = 0; i < oracleAccounts.length; i++) { - await impersonateAccount(oracleAccounts[i]); - let signature = await web3.eth.sign(candidateId, oracleAccounts[i]); - signatures.push(signature); - } - let feesEscrowBalance = await balance.current(contracts.feesEscrow); +async function setTotalRewards({ rewardEthToken, totalRewards, vault }) { + const totalSupply = await rewardEthToken.totalSupply(); + const totalPenalty = await rewardEthToken.totalPenalty(); + let delta = totalRewards.sub(totalSupply); // update total rewards - let receipt = await oracles.submitRewards( - totalRewards, - activatedValidators, - signatures, - { - from: oracleAccounts[0], - } - ); + let receipt = await rewardEthToken.updateTotalRewards(delta, { + from: vault, + }); + if (delta.isNeg()) { + delta = new BN(0); + } + if (totalPenalty.gt(delta)) { + delta = new BN(0); + } else { + delta = delta.sub(totalPenalty); + } expect(await rewardEthToken.totalSupply()).to.bignumber.equal( - totalRewards.add(feesEscrowBalance) + totalSupply.add(delta) ); - return receipt; } @@ -256,6 +194,26 @@ async function stopImpersonatingAccount(account) { }); } +async function addStakedEthToken(stakedEthToken, account, value) { + // random sETH2 holder + let holder = '0x7bb9AEFFF145afddFD4f7A455b456bCCCe88448f'; + await impersonateAccount(holder); + await stakedEthToken.transfer(account, value, { + from: holder, + }); + await stopImpersonatingAccount(holder); +} + +async function addRewardEthToken(rewardEthToken, account, value) { + // random rETH2 holder + let holder = '0x7BdDb2C97AF91f97E73F07dEB976fdFC2d2Ee93c'; + await impersonateAccount(holder); + await rewardEthToken.transfer(account, value, { + from: holder, + }); + await stopImpersonatingAccount(holder); +} + async function resetFork() { await hre.network.provider.request({ method: 'hardhat_reset', @@ -296,8 +254,6 @@ async function setupOracleAccounts({ admin, oracles, accounts }) { } module.exports = { - checkValidatorRegistered, - getDepositAmount, checkStakedEthToken, checkRewardEthToken, impersonateAccount, @@ -308,4 +264,6 @@ module.exports = { setMerkleRoot, setupOracleAccounts, registerValidators, + addStakedEthToken, + addRewardEthToken, };