diff --git a/contracts/contracts/interfaces/IDepositContract.sol b/contracts/contracts/interfaces/IDepositContract.sol
index 9d62b5d776..07f654443b 100644
--- a/contracts/contracts/interfaces/IDepositContract.sol
+++ b/contracts/contracts/interfaces/IDepositContract.sol
@@ -31,4 +31,4 @@ interface IDepositContract {
/// @notice Query the current deposit count.
/// @return The deposit count encoded as a little endian 64-bit number.
function get_deposit_count() external view returns (bytes memory);
-}
\ No newline at end of file
+}
diff --git a/contracts/contracts/mocks/BeaconChainDepositContractMock.sol b/contracts/contracts/mocks/BeaconChainDepositContractMock.sol
index d2e148cf46..3d8b995efe 100644
--- a/contracts/contracts/mocks/BeaconChainDepositContractMock.sol
+++ b/contracts/contracts/mocks/BeaconChainDepositContractMock.sol
@@ -25,13 +25,29 @@ contract BeaconChainDepositContractMock {
) external payable {
// Extended ABI length checks since dynamic types are used.
require(pubkey.length == 48, "DepositContract: invalid pubkey length");
- require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length");
- require(signature.length == 96, "DepositContract: invalid signature length");
+ require(
+ withdrawal_credentials.length == 32,
+ "DepositContract: invalid withdrawal_credentials length"
+ );
+ require(
+ signature.length == 96,
+ "DepositContract: invalid signature length"
+ );
// Check deposit amount
require(msg.value >= 1 ether, "DepositContract: deposit value too low");
- require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei");
- uint deposit_amount = msg.value / 1 gwei;
- require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high");
+ require(
+ msg.value % 1 gwei == 0,
+ "DepositContract: deposit value not multiple of gwei"
+ );
+ uint256 deposit_amount = msg.value / 1 gwei;
+ require(
+ deposit_amount <= type(uint64).max,
+ "DepositContract: deposit value too high"
+ );
+ require(
+ deposit_data_root != 0,
+ "DepositContract: invalid deposit_data_root"
+ );
}
-}
\ No newline at end of file
+}
diff --git a/contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol b/contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol
index 6a9abd7046..baa5e2bf52 100644
--- a/contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol
+++ b/contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol
@@ -7,13 +7,14 @@ import { IWETH9 } from "../../interfaces/IWETH9.sol";
/**
* @title Fee Accumulator for Native Staking SSV Strategy
* @notice This contract is setup to receive fees from processing transactions on the beacon chain
- * which includes priority fees and any MEV rewards
+ * which includes priority fees and any MEV rewards.
+ * It does NOT include swept ETH from consensus rewards or withdrawals.
* @author Origin Protocol Inc
*/
contract FeeAccumulator is Governable {
- /// @dev ETH is sent to the collector address
+ /// @notice The address the WETH is sent to on `collect` which is the Native Staking Strategy
address public immutable COLLECTOR;
- /// @notice WETH token address
+ /// @notice The address of the Wrapped ETH (WETH) token contract
address public immutable WETH_TOKEN_ADDRESS;
error CallerNotCollector(address caller, address expectedCaller);
@@ -23,14 +24,15 @@ contract FeeAccumulator is Governable {
/**
* @param _collector Address of the contract that collects the fees
+ * @param _weth Address of the Wrapped ETH (WETH) token contract
*/
constructor(address _collector, address _weth) {
COLLECTOR = _collector;
WETH_TOKEN_ADDRESS = _weth;
}
- /*
- * @notice Asserts that the caller is the collector
+ /**
+ * @dev Asserts that the caller is the collector
*/
function _assertIsCollector() internal view {
if (msg.sender != COLLECTOR) {
@@ -38,15 +40,16 @@ contract FeeAccumulator is Governable {
}
}
- /*
- * @notice Send all the ETH to the collector
+ /**
+ * @notice Converts ETH to WETH and sends the WETH to the collector
+ * @return weth The amount of WETH sent to the collector
*/
- function collect() external returns (uint256 wethReturned) {
+ function collect() external returns (uint256 weth) {
_assertIsCollector();
- wethReturned = address(this).balance;
- if (wethReturned > 0) {
- IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: wethReturned }();
- IWETH9(WETH_TOKEN_ADDRESS).transfer(COLLECTOR, wethReturned);
+ weth = address(this).balance;
+ if (weth > 0) {
+ IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: weth }();
+ IWETH9(WETH_TOKEN_ADDRESS).transfer(COLLECTOR, weth);
}
}
}
diff --git a/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol b/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol
index 7d06295e35..7bbd0345bd 100644
--- a/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol
+++ b/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol
@@ -5,6 +5,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
+import { ISSVNetwork, Cluster } from "../../interfaces/ISSVNetwork.sol";
import { IWETH9 } from "../../interfaces/IWETH9.sol";
import { FeeAccumulator } from "./FeeAccumulator.sol";
import { ValidatorAccountant } from "./ValidatorAccountant.sol";
@@ -39,7 +40,7 @@ contract NativeStakingSSVStrategy is
error EmptyRecipient();
error NotWeth();
- error InsuffiscientWethBalance(
+ error InsufficientWethBalance(
uint256 requiredBalance,
uint256 availableBalance
);
@@ -60,7 +61,12 @@ contract NativeStakingSSVStrategy is
address _beaconChainDepositContract
)
InitializableAbstractStrategy(_baseConfig)
- ValidatorAccountant(_wethAddress, _baseConfig.vaultAddress, _beaconChainDepositContract, _ssvNetwork)
+ ValidatorAccountant(
+ _wethAddress,
+ _baseConfig.vaultAddress,
+ _beaconChainDepositContract,
+ _ssvNetwork
+ )
{
SSV_TOKEN_ADDRESS = _ssvToken;
FEE_ACCUMULATOR_ADDRESS = _feeAccumulator;
@@ -98,7 +104,8 @@ contract NativeStakingSSVStrategy is
beaconChainRewardWETH;
}
- /// @notice Collect accumulated WETH & SSV tokens and send to the Harvester.
+ /// @notice Convert accumulated ETH to WETH and send to the Harvester.
+ /// Only callable by the Harvester.
function collectRewardTokens()
external
virtual
@@ -129,7 +136,7 @@ contract NativeStakingSSVStrategy is
if (balance > 0) {
if (address(rewardToken) == WETH_TOKEN_ADDRESS) {
if (beaconChainRewardWETH > balance) {
- revert InsuffiscientWethBalance(
+ revert InsufficientWethBalance(
beaconChainRewardWETH,
balance
);
@@ -232,9 +239,9 @@ contract NativeStakingSSVStrategy is
function _abstractSetPToken(address _asset, address) internal override {}
/// @notice Returns the total value of (W)ETH that is staked to the validators
- /// and also present on the native staking and fee accumulator contracts
+ /// and also present on the native staking and fee accumulator contracts.
/// @param _asset Address of weth asset
- /// @return balance Total value of (W)ETH
+ /// @return balance Total value of (W)ETH
function checkBalance(address _asset)
external
view
@@ -254,14 +261,13 @@ contract NativeStakingSSVStrategy is
_pause();
}
- /// @dev Retuns bool indicating whether asset is supported by strategy
- /// @param _asset Address of the asset
+ /// @notice Returns bool indicating whether asset is supported by strategy.
+ /// @param _asset The address of the asset token.
function supportsAsset(address _asset) public view override returns (bool) {
return _asset == WETH_TOKEN_ADDRESS;
}
- /// @notice Approve the spending of all assets
- /// @dev Approves the SSV Network contract to transfer SSV tokens for deposits
+ /// @notice Approves the SSV Network contract to transfer SSV tokens for deposits
function safeApproveAllTokens() external override {
/// @dev Approves the SSV Network contract to transfer SSV tokens for deposits
IERC20(SSV_TOKEN_ADDRESS).approve(
@@ -271,17 +277,20 @@ contract NativeStakingSSVStrategy is
}
/// @notice Deposits more SSV Tokens to the SSV Network contract which is used to pay the SSV Operators.
- /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds
- /// uses "onlyStrategist" modifier so continuous fron-running can't DOS our maintenance service
- /// that tries to top us SSV tokens.
+ /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds.
+ /// uses "onlyStrategist" modifier so continuous front-running can't DOS our maintenance service
+ /// that tries to top up SSV tokens.
+ /// @param cluster The SSV cluster details that must be derived from emitted events from the SSVNetwork contract.
function depositSSV(
uint64[] memory operatorIds,
uint256 amount,
Cluster memory cluster
- )
- onlyStrategist
- external {
- // address SSV_NETWORK_ADDRESS = lrtConfig.getContract(LRTConstants.SSV_NETWORK);
- // ISSVNetwork(SSV_NETWORK_ADDRESS).deposit(address(this), operatorIds, amount, cluster);
+ ) external onlyStrategist {
+ ISSVNetwork(SSV_NETWORK_ADDRESS).deposit(
+ address(this),
+ operatorIds,
+ amount,
+ cluster
+ );
}
}
diff --git a/contracts/contracts/strategies/NativeStaking/README.md b/contracts/contracts/strategies/NativeStaking/README.md
new file mode 100644
index 0000000000..13e280eb9b
--- /dev/null
+++ b/contracts/contracts/strategies/NativeStaking/README.md
@@ -0,0 +1,21 @@
+# Diagrams
+
+## Native Staking SSV Strategy
+
+### Hierarchy
+
+![Native Staking SSV Strategy Hierarchy](../../../docs/NativeStakingSSVStrategyHierarchy.svg)
+
+### Squashed
+
+![Native Staking SSV Strategy Squashed](../../../docs/NativeStakingSSVStrategySquashed.svg)
+
+### Storage
+
+![Native Staking SSV Strategy Storage](../../../docs/NativeStakingSSVStrategyStorage.svg)
+
+## Fee Accumulator
+
+### Squashed
+
+![Fee Accumulator Squashed](../../../docs/FeeAccumulatorSquashed.svg)
diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol b/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol
index e85b808129..8570e96f16 100644
--- a/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol
+++ b/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol
@@ -3,6 +3,7 @@ pragma solidity ^0.8.0;
import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol";
import { ValidatorRegistrator } from "./ValidatorRegistrator.sol";
+import { IVault } from "../../interfaces/IVault.sol";
import { IWETH9 } from "../../interfaces/IWETH9.sol";
/// @title Accountant of the rewards Beacon Chain ETH
@@ -10,6 +11,10 @@ import { IWETH9 } from "../../interfaces/IWETH9.sol";
/// or partial withdrawals
/// @author Origin Protocol Inc
abstract contract ValidatorAccountant is ValidatorRegistrator {
+ /// @notice The maximum amount of ETH that can be staked by a validator
+ /// @dev this can change in the future with EIP-7251, Increase the MAX_EFFECTIVE_BALANCE
+ uint256 public constant MAX_STAKE = 32 ether;
+ /// @notice Address of the OETH Vault proxy contract
address public immutable VAULT_ADDRESS;
/// @dev The WETH present on this contract will come from 2 sources:
@@ -23,14 +28,12 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
/// present as a result of a deposit.
uint256 public beaconChainRewardWETH = 0;
- /// @dev start of fuse interval
+ /// @notice start of fuse interval
uint256 public fuseIntervalStart = 0;
- /// @dev end of fuse interval
+ /// @notice end of fuse interval
uint256 public fuseIntervalEnd = 0;
- /// @dev Governor that can manually correct the accounting
+ /// @notice Governor that can manually correct the accounting
address public accountingGovernor;
- /// @dev Strategist that can pause the accounting
- address public strategist;
uint256[50] private __gap;
@@ -40,12 +43,12 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
uint256 start,
uint256 end
);
- event AccuntingFullyWithdrawnValidator(
+ event AccountingFullyWithdrawnValidator(
uint256 noOfValidators,
uint256 remainingValidators,
uint256 wethSentToVault
);
- event AccuntingValidatorSlashed(
+ event AccountingValidatorSlashed(
uint256 remainingValidators,
uint256 wethSentToVault
);
@@ -54,10 +57,6 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
address newAddress
);
event AccountingBeaconChainRewards(uint256 amount);
- event StrategistAddressChanged(
- address oldStrategist,
- address newStrategist
- );
event AccountingManuallyFixed(
uint256 oldActiveDepositedValidators,
@@ -85,7 +84,10 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
/// @dev Throws if called by any account other than the Strategist
modifier onlyStrategist() {
- require(msg.sender == strategist, "Caller is not the Strategist");
+ require(
+ msg.sender == IVault(VAULT_ADDRESS).strategistAddr(),
+ "Caller is not the Strategist"
+ );
_;
}
@@ -93,8 +95,18 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
/// @param _vaultAddress Address of the Vault
/// @param _beaconChainDepositContract Address of the beacon chain deposit contract
/// @param _ssvNetwork Address of the SSV Network contract
- constructor(address _wethAddress, address _vaultAddress, address _beaconChainDepositContract, address _ssvNetwork)
- ValidatorRegistrator(_wethAddress, _beaconChainDepositContract, _ssvNetwork) {
+ constructor(
+ address _wethAddress,
+ address _vaultAddress,
+ address _beaconChainDepositContract,
+ address _ssvNetwork
+ )
+ ValidatorRegistrator(
+ _wethAddress,
+ _beaconChainDepositContract,
+ _ssvNetwork
+ )
+ {
VAULT_ADDRESS = _vaultAddress;
}
@@ -103,11 +115,6 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
accountingGovernor = _address;
}
- function setStrategist(address _address) external onlyGovernor {
- emit StrategistAddressChanged(strategist, _address);
- strategist = _address;
- }
-
/// @notice set fuse interval values
function setFuseInterval(
uint256 _fuseIntervalStart,
@@ -133,19 +140,24 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
fuseIntervalEnd = _fuseIntervalEnd;
}
+ /* solhint-disable max-line-length */
/// This notion page offers a good explanation of how the accounting functions
/// https://www.notion.so/originprotocol/Limited-simplified-native-staking-accounting-67a217c8420d40678eb943b9da0ee77d
- /// In short after dividing by 32 if the ETH remaining on the contract falls between 0 and fuseIntervalStart the accounting
+ /// In short, after dividing by 32 if the ETH remaining on the contract falls between 0 and fuseIntervalStart the accounting
/// function will treat that ETH as a Beacon Chain Reward ETH.
/// On the contrary if after dividing by 32 the ETH remaining on the contract falls between fuseIntervalEnd and 32 the
/// accounting function will treat that as a validator slashing.
/// @notice Perform the accounting attributing beacon chain ETH to either full or partial withdrawals. Returns true when
- /// accounting is valid and fuse isn't "blown". Returns false when fuse is blown
+ /// accounting is valid and fuse isn't "blown". Returns false when fuse is blown.
/// @dev This function could in theory be permission-less but lets allow only the Registrator (Defender Action) to call it
- /// for now
- function doAccounting() external onlyRegistrator returns (bool accountingValid) {
+ /// for now.
+ /* solhint-enable max-line-length */
+ function doAccounting()
+ external
+ onlyRegistrator
+ returns (bool accountingValid)
+ {
uint256 ethBalance = address(this).balance;
- uint256 MAX_STAKE = 32 ether;
accountingValid = true;
// send the WETH that is from fully withdrawn validators to the Vault
@@ -157,7 +169,7 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: wethToVault }();
IWETH9(WETH_TOKEN_ADDRESS).transfer(VAULT_ADDRESS, wethToVault);
- emit AccuntingFullyWithdrawnValidator(
+ emit AccountingFullyWithdrawnValidator(
fullyWithdrawnValidators,
activeDepositedValidators,
wethToVault
@@ -173,6 +185,7 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
// Beacon chain rewards swept (partial validator withdrawals)
if (ethRemaining <= fuseIntervalStart) {
IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: ethRemaining }();
+ // solhint-disable-next-line reentrancy
beaconChainRewardWETH += ethRemaining;
emit AccountingBeaconChainRewards(ethRemaining);
}
@@ -182,7 +195,7 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
IWETH9(WETH_TOKEN_ADDRESS).transfer(VAULT_ADDRESS, ethRemaining);
activeDepositedValidators -= 1;
- emit AccuntingValidatorSlashed(
+ emit AccountingValidatorSlashed(
activeDepositedValidators,
ethRemaining
);
diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol
index 2ee2a87fa1..1674b47730 100644
--- a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol
+++ b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol
@@ -15,16 +15,16 @@ struct ValidatorStakeData {
/**
* @title Registrator of the validators
- * @notice This contract implements all the required functionality to register validators
+ * @notice This contract implements all the required functionality to register, exit and remove validators.
* @author Origin Protocol Inc
*/
abstract contract ValidatorRegistrator is Governable, Pausable {
- /// @notice The Wrapped ETH (WETH) contract address
+ /// @notice The address of the Wrapped ETH (WETH) token contract
address public immutable WETH_TOKEN_ADDRESS;
- /// @notice Address of the beacon chain deposit contract
+ /// @notice The address of the beacon chain deposit contract
address public immutable BEACON_CHAIN_DEPOSIT_CONTRACT;
- /// @notice SSV Network contract used to interface with
- address public immutable SSV_NETWORK_ADDRESS;
+ /// @notice The address of the SSV Network contract used to interface with
+ address public immutable SSV_NETWORK_ADDRESS;
/// @notice Address of the registrator - allowed to register, exit and remove validators
address public validatorRegistrator;
@@ -66,7 +66,11 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
/// @param _wethAddress Address of the Erc20 WETH Token contract
/// @param _beaconChainDepositContract Address of the beacon chain deposit contract
/// @param _ssvNetwork Address of the SSV Network contract
- constructor(address _wethAddress, address _beaconChainDepositContract, address _ssvNetwork) {
+ constructor(
+ address _wethAddress,
+ address _beaconChainDepositContract,
+ address _ssvNetwork
+ ) {
WETH_TOKEN_ADDRESS = _wethAddress;
BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract;
SSV_NETWORK_ADDRESS = _ssvNetwork;
@@ -88,12 +92,13 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
/// @notice Stakes WETH to the node validators
/// @param validators A list of validator data needed to stake.
- /// The ValidatorStakeData struct contains the pubkey, signature and depositDataRoot.
- /// @dev Only accounts with the Operator role can call this function.
- function stakeEth(ValidatorStakeData[] calldata validators)
+ /// The `ValidatorStakeData` struct contains the pubkey, signature and depositDataRoot.
+ /// Only the registrator can call this function.
+ function stakeEth(ValidatorStakeData[] calldata validators)
+ external
onlyRegistrator
whenNotPaused
- external {
+ {
uint256 requiredWETH = validators.length * 32 ether;
uint256 wethBalance = getWETHBalanceEligibleForStaking();
if (wethBalance < requiredWETH) {
@@ -104,15 +109,22 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
IWETH9(WETH_TOKEN_ADDRESS).withdraw(wethBalance);
// For each validator
- for (uint256 i = 0; i < validators.length;) {
+ for (uint256 i = 0; i < validators.length; ) {
bytes32 pubkeyHash = keccak256(validators[i].pubkey);
VALIDATOR_STATE currentState = validatorsStates[pubkeyHash];
if (currentState != VALIDATOR_STATE.REGISTERED) {
- revert ValidatorInUnexpectedState(validators[i].pubkey, currentState);
+ revert ValidatorInUnexpectedState(
+ validators[i].pubkey,
+ currentState
+ );
}
- _stakeEth(validators[i].pubkey, validators[i].signature, validators[i].depositDataRoot);
+ _stakeEth(
+ validators[i].pubkey,
+ validators[i].signature,
+ validators[i].depositDataRoot
+ );
validatorsStates[pubkeyHash] = VALIDATOR_STATE.STAKED;
unchecked {
@@ -121,51 +133,61 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
}
}
- /// @dev Deposit WETH to the beacon chain deposit contract
- /// @dev The public functions that call this internal function are responsible for access control.
- function _stakeEth(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) internal {
+ /// @dev Deposit WETH to the beacon chain deposit contract.
+ /// The public functions that call this internal function are responsible for access control.
+ function _stakeEth(
+ bytes calldata pubkey,
+ bytes calldata signature,
+ bytes32 depositDataRoot
+ ) internal {
/* 0x01 to indicate that withdrawal credentials will contain an EOA address that the sweeping function
* can sweep funds to.
* bytes11(0) to fill up the required zeros
* remaining bytes20 are for the address
*/
- bytes memory withdrawal_credentials = abi.encodePacked(bytes1(0x01), bytes11(0), address(this));
+ bytes memory withdrawal_credentials = abi.encodePacked(
+ bytes1(0x01),
+ bytes11(0),
+ address(this)
+ );
IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit(
- pubkey, withdrawal_credentials, signature, depositDataRoot
+ pubkey,
+ withdrawal_credentials,
+ signature,
+ depositDataRoot
);
activeDepositedValidators += 1;
emit ETHStaked(pubkey, 32 ether, withdrawal_credentials);
-
}
- /// @dev Registers a new validator in the SSV Cluster
+ /// @notice Registers a new validator in the SSV Cluster.
+ /// Only the registrator can call this function.
function registerSsvValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
bytes calldata sharesData,
uint256 amount,
Cluster calldata cluster
- )
- external
- onlyRegistrator
- whenNotPaused
- {
- ISSVNetwork(SSV_NETWORK_ADDRESS).registerValidator(publicKey, operatorIds, sharesData, amount, cluster);
+ ) external onlyRegistrator whenNotPaused {
+ ISSVNetwork(SSV_NETWORK_ADDRESS).registerValidator(
+ publicKey,
+ operatorIds,
+ sharesData,
+ amount,
+ cluster
+ );
validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.REGISTERED;
emit SSVValidatorRegistered(publicKey, operatorIds);
}
- /// @dev Exit a validator from the Beacon chain.
- /// The staked ETH will be sent to the EigenPod.
+ /// @notice Exit a validator from the Beacon chain.
+ /// The staked ETH will eventually swept to this native staking strategy.
+ /// Only the registrator can call this function.
function exitSsvValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds
- )
- external
- onlyRegistrator
- whenNotPaused
- {
+ ) external onlyRegistrator whenNotPaused {
VALIDATOR_STATE currentState = validatorsStates[keccak256(publicKey)];
if (currentState != VALIDATOR_STATE.STAKED) {
revert ValidatorInUnexpectedState(publicKey, currentState);
@@ -177,24 +199,25 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.EXITING;
}
- /// @dev Remove a validator from the SSV Cluster.
+ /// @notice Remove a validator from the SSV Cluster.
/// Make sure `exitSsvValidator` is called before and the validate has exited the Beacon chain.
/// If removed before the validator has exited the beacon chain will result in the validator being slashed.
+ /// Only the registrator can call this function.
function removeSsvValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
Cluster calldata cluster
- )
- external
- onlyRegistrator
- whenNotPaused
- {
+ ) external onlyRegistrator whenNotPaused {
VALIDATOR_STATE currentState = validatorsStates[keccak256(publicKey)];
if (currentState != VALIDATOR_STATE.EXITING) {
revert ValidatorInUnexpectedState(publicKey, currentState);
}
- ISSVNetwork(SSV_NETWORK_ADDRESS).removeValidator(publicKey, operatorIds, cluster);
+ ISSVNetwork(SSV_NETWORK_ADDRESS).removeValidator(
+ publicKey,
+ operatorIds,
+ cluster
+ );
emit SSVValidatorExitCompleted(publicKey, operatorIds);
validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.EXIT_COMPLETE;
diff --git a/contracts/deploy/001_core.js b/contracts/deploy/001_core.js
index ed14d833ec..b3cb95cea2 100644
--- a/contracts/deploy/001_core.js
+++ b/contracts/deploy/001_core.js
@@ -764,7 +764,7 @@ const deployNativeStakingSSVStrategy = async () => {
assetAddresses.SSV, // ssvToken
assetAddresses.SSVNetwork, // ssvNetwork
dFeeAccumulatorProxy.address, // feeAccumulator
- assetAddresses.beaconChainDepositContract // depositContractMock
+ assetAddresses.beaconChainDepositContract, // depositContractMock
]
);
const cStrategyImpl = await ethers.getContractAt(
diff --git a/contracts/docs/FeeAccumulatorSquashed.svg b/contracts/docs/FeeAccumulatorSquashed.svg
new file mode 100644
index 0000000000..3c2c1558ab
--- /dev/null
+++ b/contracts/docs/FeeAccumulatorSquashed.svg
@@ -0,0 +1,52 @@
+
+
+
+
+
diff --git a/contracts/docs/NativeStakingSSVStrategyHierarchy.svg b/contracts/docs/NativeStakingSSVStrategyHierarchy.svg
new file mode 100644
index 0000000000..3007a5a443
--- /dev/null
+++ b/contracts/docs/NativeStakingSSVStrategyHierarchy.svg
@@ -0,0 +1,142 @@
+
+
+
+
+
diff --git a/contracts/docs/NativeStakingSSVStrategySquashed.svg b/contracts/docs/NativeStakingSSVStrategySquashed.svg
new file mode 100644
index 0000000000..838bee6efe
--- /dev/null
+++ b/contracts/docs/NativeStakingSSVStrategySquashed.svg
@@ -0,0 +1,154 @@
+
+
+
+
+
diff --git a/contracts/docs/NativeStakingSSVStrategyStorage.svg b/contracts/docs/NativeStakingSSVStrategyStorage.svg
new file mode 100644
index 0000000000..41964933b0
--- /dev/null
+++ b/contracts/docs/NativeStakingSSVStrategyStorage.svg
@@ -0,0 +1,177 @@
+
+
+
+
+
diff --git a/contracts/docs/generate.sh b/contracts/docs/generate.sh
index 1299504db7..69ab22c21c 100644
--- a/contracts/docs/generate.sh
+++ b/contracts/docs/generate.sh
@@ -82,6 +82,11 @@ sol2uml .. -v -hv -hf -he -hs -hl -hi -b Generalized4626Strategy -o Generalized4
sol2uml .. -s -d 0 -b Generalized4626Strategy -o Generalized4626StrategySquashed.svg
sol2uml storage .. -c Generalized4626Strategy -o Generalized4626StrategyStorage.svg --hideExpand ______gap,_reserved,__gap
+sol2uml .. -v -hv -hf -he -hs -hl -hi -b NativeStakingSSVStrategy -o NativeStakingSSVStrategyHierarchy.svg
+sol2uml .. -s -d 0 -b NativeStakingSSVStrategy -o NativeStakingSSVStrategySquashed.svg
+sol2uml .. -s -d 0 -b FeeAccumulator -o FeeAccumulatorSquashed.svg
+sol2uml storage .. -c NativeStakingSSVStrategy -o NativeStakingSSVStrategyStorage.svg --hideExpand __gap,______gap,_reserved
+
sol2uml .. -v -hv -hf -he -hs -hl -hi -b MorphoAaveStrategy -o MorphoAaveStrategyHierarchy.svg
sol2uml .. -s -d 0 -b MorphoAaveStrategy -o MorphoAaveStrategySquashed.svg
sol2uml storage .. -c MorphoAaveStrategy -o MorphoAaveStrategyStorage.svg --hideExpand ______gap,_reserved
diff --git a/contracts/test/_fixture.js b/contracts/test/_fixture.js
index a3c4b13836..f969368877 100644
--- a/contracts/test/_fixture.js
+++ b/contracts/test/_fixture.js
@@ -1422,7 +1422,7 @@ async function nativeStakingSSVStrategyFixture() {
.setAssetDefaultStrategy(weth.address, nativeStakingSSVStrategy.address);
} else {
const { governorAddr } = await getNamedAccounts();
- const { oethVault, weth, nativeStakingSSVStrategy, strategist } = fixture;
+ const { oethVault, weth, nativeStakingSSVStrategy } = fixture;
const sGovernor = await ethers.provider.getSigner(governorAddr);
// Approve Strategy
@@ -1454,10 +1454,6 @@ async function nativeStakingSSVStrategyFixture() {
await nativeStakingSSVStrategy
.connect(sGovernor)
.setAccountingGovernor(governorAddr);
-
- await nativeStakingSSVStrategy
- .connect(sGovernor)
- .setStrategist(strategist.address);
}
return fixture;
diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js
index 62c72dfbd3..ae8f1a05d5 100644
--- a/contracts/test/helpers.js
+++ b/contracts/test/helpers.js
@@ -428,7 +428,6 @@ const getAssetAddresses = async (deployments) => {
SSV: addresses.mainnet.SSV,
SSVNetwork: addresses.mainnet.SSVNetwork,
beaconChainDepositContract: addresses.mainnet.beaconChainDepositContract,
-
};
} else {
const addressMap = {
@@ -476,7 +475,9 @@ const getAssetAddresses = async (deployments) => {
BAL: (await deployments.get("MockBAL")).address,
SSV: (await deployments.get("MockSSV")).address,
SSVNetwork: (await deployments.get("MockSSVNetwork")).address,
- beaconChainDepositContract: (await deployments.get("BeaconChainDepositContractMock")).address,
+ beaconChainDepositContract: (
+ await deployments.get("BeaconChainDepositContractMock")
+ ).address,
};
try {
diff --git a/contracts/test/strategies/nativeSSVStaking.js b/contracts/test/strategies/nativeSSVStaking.js
index 037ff17c8f..910a5d8ba0 100644
--- a/contracts/test/strategies/nativeSSVStaking.js
+++ b/contracts/test/strategies/nativeSSVStaking.js
@@ -186,16 +186,6 @@ describe("Unit test: Native SSV Staking Strategy", function () {
).to.be.revertedWith("Caller is not the Governor");
});
- it("Only governor can change the strategist", async () => {
- const { nativeStakingSSVStrategy, strategist } = fixture;
-
- await expect(
- nativeStakingSSVStrategy
- .connect(strategist)
- .setStrategist(strategist.address)
- ).to.be.revertedWith("Caller is not the Governor");
- });
-
it("Change the accounting governor", async () => {
const { nativeStakingSSVStrategy, governor, strategist } = fixture;
@@ -217,26 +207,6 @@ describe("Unit test: Native SSV Staking Strategy", function () {
strategist.address
);
});
-
- it("Change the strategist", async () => {
- const { nativeStakingSSVStrategy, governor, strategist } = fixture;
-
- const tx = await nativeStakingSSVStrategy
- .connect(governor)
- .setStrategist(governor.address);
-
- const events = (await tx.wait()).events || [];
- const strategistAddressChanged = events.find(
- (e) => e.event === "StrategistAddressChanged"
- );
-
- expect(strategistAddressChanged).to.not.be.undefined;
- expect(strategistAddressChanged.event).to.equal(
- "StrategistAddressChanged"
- );
- expect(strategistAddressChanged.args[0]).to.equal(strategist.address);
- expect(strategistAddressChanged.args[1]).to.equal(governor.address);
- });
});
describe("Accounting", function () {
@@ -345,7 +315,7 @@ describe("Unit test: Native SSV Staking Strategy", function () {
}
const WithdrawnEvent = events.find(
- (e) => e.event === "AccuntingFullyWithdrawnValidator"
+ (e) => e.event === "AccountingFullyWithdrawnValidator"
);
if (expectedValidatorsFullWithdrawals > 0) {
expect(WithdrawnEvent).to.not.be.undefined;
@@ -374,7 +344,7 @@ describe("Unit test: Native SSV Staking Strategy", function () {
}
const SlashEvent = events.find(
- (e) => e.event === "AccuntingValidatorSlashed"
+ (e) => e.event === "AccountingValidatorSlashed"
);
if (slashDetected) {
expect(SlashEvent).to.not.be.undefined;
@@ -567,7 +537,7 @@ describe("Unit test: Native SSV Staking Strategy", function () {
const {
nativeStakingSSVStrategy,
governor,
- strategist,
+ // strategist,
oethHarvester,
weth,
josh,
@@ -642,7 +612,7 @@ describe("Unit test: Native SSV Staking Strategy", function () {
nativeStakingSSVStrategy,
governor,
strategist,
- oethHarvester,
+ // oethHarvester,
weth,
josh,
} = fixture;
@@ -651,11 +621,11 @@ describe("Unit test: Native SSV Staking Strategy", function () {
beaconChainRewardEth,
wethFromDeposits,
expectedEthSentToHarvester,
- nrOfActiveDepositedValidators
+ nrOfActiveDepositedValidators,
} = testCase;
const feeAccumulatorAddress =
await nativeStakingSSVStrategy.FEE_ACCUMULATOR_ADDRESS();
- const sHarvester = await impersonateAndFund(oethHarvester.address);
+ // const sHarvester = await impersonateAndFund(oethHarvester.address);
// setup state
if (beaconChainRewardEth.gt(BigNumber.from("0"))) {
@@ -690,8 +660,14 @@ describe("Unit test: Native SSV Staking Strategy", function () {
// run the accounting
await nativeStakingSSVStrategy.connect(governor).doAccounting();
- expect(await nativeStakingSSVStrategy.checkBalance(weth.address)).to.equal(
- expectedEthSentToHarvester.add(BigNumber.from(`${nrOfActiveDepositedValidators}`).mul(utils.parseEther("32")))
+ expect(
+ await nativeStakingSSVStrategy.checkBalance(weth.address)
+ ).to.equal(
+ expectedEthSentToHarvester.add(
+ BigNumber.from(`${nrOfActiveDepositedValidators}`).mul(
+ utils.parseEther("32")
+ )
+ )
);
});
}
diff --git a/contracts/test/strategies/nativeSsvStaking.fork-test.js b/contracts/test/strategies/nativeSsvStaking.fork-test.js
index f5d6b1512d..3fbd13a3c5 100644
--- a/contracts/test/strategies/nativeSsvStaking.fork-test.js
+++ b/contracts/test/strategies/nativeSsvStaking.fork-test.js
@@ -1,15 +1,11 @@
-const hre = require("hardhat");
const { expect } = require("chai");
-const { units, oethUnits, isCI } = require("../helpers");
const addresses = require("../../utils/addresses");
const {
createFixtureLoader,
nativeStakingSSVStrategyFixture,
} = require("./../_fixture");
-const { impersonateAndFund } = require("../../utils/signers");
-const { setERC20TokenBalance } = require("../_fund");
const loadFixture = createFixtureLoader(nativeStakingSSVStrategyFixture);
@@ -17,7 +13,7 @@ describe("ForkTest: Native SSV Staking Strategy", function () {
this.timeout(0);
// Retry up to 3 times on CI
- this.retries(isCI ? 3 : 0);
+ // this.retries(isCI ? 3 : 0);
let fixture;
beforeEach(async () => {
@@ -26,7 +22,7 @@ describe("ForkTest: Native SSV Staking Strategy", function () {
describe("Initial setup", function () {
it("Should verify the initial state", async () => {
- const { weth, nativeStakingSSVStrategy } = fixture;
+ const { nativeStakingSSVStrategy } = fixture;
await expect(
await nativeStakingSSVStrategy.WETH_TOKEN_ADDRESS()
).to.equal(addresses.mainnet.WETH, "Incorrect WETH address set");
diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js
index adc918518c..36c557e3b1 100644
--- a/contracts/utils/addresses.js
+++ b/contracts/utils/addresses.js
@@ -243,7 +243,8 @@ addresses.mainnet.CurveCVXPool = "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4";
// SSV network
addresses.mainnet.SSV = "0x9D65fF81a3c488d585bBfb0Bfe3c7707c7917f54";
addresses.mainnet.SSVNetwork = "0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1";
-addresses.mainnet.beaconChainDepositContract = "0x00000000219ab540356cbb839cbe05303d7705fa";
+addresses.mainnet.beaconChainDepositContract =
+ "0x00000000219ab540356cbb839cbe05303d7705fa";
// Arbitrum One
addresses.arbitrumOne = {};