Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion contracts/contracts/mocks/MockSSVNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ pragma solidity ^0.8.0;
import { Cluster } from "./../interfaces/ISSVNetwork.sol";

contract MockSSVNetwork {
error ValidatorAlreadyExists();

mapping(bytes32 => bool) public validatorExists;

function registerValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
bytes calldata sharesData,
Cluster memory cluster
) external payable {}
) external payable {
bytes32 pubKeyHash = keccak256(publicKey);
if (validatorExists[pubKeyHash]) revert ValidatorAlreadyExists();
validatorExists[pubKeyHash] = true;
}

function bulkRegisterValidator(
bytes[] calldata publicKeys,
Expand Down
9 changes: 9 additions & 0 deletions contracts/contracts/proxies/Proxies.sol
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@ contract CompoundingStakingSSVStrategyProxy is

}

/**
* @notice CompoundingStakingStrategyProxy delegates calls to a CompoundingStakingStrategy implementation
*/
contract CompoundingStakingStrategyProxy is
InitializeGovernedUpgradeabilityProxy
{

}

/**
* @notice OUSDMorphoV2StrategyProxy delegates calls to a Generalized4626Strategy implementation
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
import { IWETH9 } from "../../interfaces/IWETH9.sol";
import { CompoundingValidatorManager } from "./CompoundingValidatorManager.sol";
import { ISSVNetwork, Cluster } from "../../interfaces/ISSVNetwork.sol";
import { CompoundingStakingStrategy } from "./CompoundingStakingStrategy.sol";

/// @title Compounding Staking SSV Strategy
/// @notice Strategy to deploy funds into DVT validators powered by the SSV Network
/// @author Origin Protocol Inc
contract CompoundingStakingSSVStrategy is
CompoundingValidatorManager,
InitializableAbstractStrategy
{
contract CompoundingStakingSSVStrategy is CompoundingStakingStrategy {
/// @notice The address of the SSV Network contract used to interface with
address internal immutable SSV_NETWORK;

// For future use
uint256[50] private __gap;

error CannotRemoveSsvValidator(); // 0x2c45bd75
error NotRegisteredOrVerified(); // 0x99088a6b

event SSVValidatorRemoved(bytes32 indexed pubKeyHash, uint64[] operatorIds);

/// @param _baseConfig Base strategy config with
/// `platformAddress` not used so empty address
/// `vaultAddress` the address of the OETH Vault contract
Expand All @@ -33,187 +35,122 @@ contract CompoundingStakingSSVStrategy is
address _beaconProofs,
uint64 _beaconGenesisTimestamp
)
InitializableAbstractStrategy(_baseConfig)
CompoundingValidatorManager(
CompoundingStakingStrategy(
_baseConfig,
_wethAddress,
_baseConfig.vaultAddress,
_beaconChainDepositContract,
_ssvNetwork,
_beaconProofs,
_beaconGenesisTimestamp
)
{
// Make sure nobody owns the implementation contract
_setGovernor(address(0));
}

/// @notice Set up initial internal state including
/// 1. approving the SSVNetwork to transfer SSV tokens from this strategy contract
/// @param _rewardTokenAddresses Not used so empty array
/// @param _assets Not used so empty array
/// @param _pTokens Not used so empty array
/// @param _initialDepositAmountWei The amount of ETH required for the first deposit to a new validator.
function initialize(
address[] memory _rewardTokenAddresses,
address[] memory _assets,
address[] memory _pTokens,
uint256 _initialDepositAmountWei
) external onlyGovernor initializer {
InitializableAbstractStrategy._initialize(
_rewardTokenAddresses,
_assets,
_pTokens
);
_setInitialDepositAmountWei(_initialDepositAmountWei);
SSV_NETWORK = _ssvNetwork;
}

/// @notice Unlike other strategies, this does not deposit assets into the underlying platform.
/// It just checks the asset is WETH and emits the Deposit event.
/// To deposit WETH into validators, `registerSsvValidator` and `stakeEth` must be used.
/// @param _asset Address of the WETH token.
/// @param _amount Amount of WETH that was transferred to the strategy by the vault.
function deposit(address _asset, uint256 _amount)
external
override
onlyVault
nonReentrant
{
require(_asset == WETH, "Unsupported asset");
require(_amount > 0, "Must deposit something");

// Account for the new WETH
depositedWethAccountedFor += _amount;
/**
*
* Validator Management
*
*/

emit Deposit(_asset, address(0), _amount);
// slither-disable-start reentrancy-no-eth
/// @notice Registers a single validator in a SSV Cluster.
/// Only the Registrator can call this function.
/// @param publicKey The public key of the validator
/// @param operatorIds The operator IDs of the SSV Cluster
/// @param sharesData The shares data for the validator
/// @param cluster The SSV cluster details including the validator count and SSV balance
function registerSsvValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
bytes calldata sharesData,
Cluster calldata cluster
) external payable onlyRegistrator whenNotPaused {
// Hash the public key using the Beacon Chain's format
bytes32 pubKeyHash = _hashPubKey(publicKey);

// Store the validator state as registered
validator[pubKeyHash].state = ValidatorState.REGISTERED;

ISSVNetwork(SSV_NETWORK).registerValidator{ value: msg.value }(
publicKey,
operatorIds,
sharesData,
cluster
);
}

/// @notice Unlike other strategies, this does not deposit assets into the underlying platform.
/// It just emits the Deposit event.
/// To deposit WETH into validators `registerSsvValidator` and `stakeEth` must be used.
function depositAll() external override onlyVault nonReentrant {
uint256 wethBalance = IERC20(WETH).balanceOf(address(this));
uint256 newWeth = wethBalance - depositedWethAccountedFor;

if (newWeth > 0) {
// Account for the new WETH
depositedWethAccountedFor = wethBalance;

emit Deposit(WETH, address(0), newWeth);
/// @notice Remove the validator from the SSV Cluster after:
/// - the validator has been exited from `validatorWithdrawal` or slashed
/// - the validator has incorrectly registered and can not be staked to
/// - the initial deposit was front-run and the withdrawal address is not this strategy's address.
/// Make sure `validatorWithdrawal` is called with a zero amount and the validator 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.
/// @param publicKey The public key of the validator
/// @param operatorIds The operator IDs of the SSV Cluster
/// @param cluster The SSV cluster details including the validator count and SSV balance
function removeSsvValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
Cluster calldata cluster
) external onlyRegistrator {
// Hash the public key using the Beacon Chain's format
bytes32 pubKeyHash = _hashPubKey(publicKey);
ValidatorState currentState = validator[pubKeyHash].state;
// Can remove SSV validators that were incorrectly registered and can not be deposited to.
if (
currentState != ValidatorState.REGISTERED &&
currentState != ValidatorState.EXITED &&
currentState != ValidatorState.INVALID
) {
revert CannotRemoveSsvValidator();
}
}

/// @notice Withdraw ETH and WETH from this strategy contract.
/// @param _recipient Address to receive withdrawn assets.
/// @param _asset Address of the WETH token.
/// @param _amount Amount of WETH to withdraw.
function withdraw(
address _recipient,
address _asset,
uint256 _amount
) external override nonReentrant {
require(_asset == WETH, "Unsupported asset");
require(
msg.sender == vaultAddress || msg.sender == validatorRegistrator,
"Caller not Vault or Registrator"
validator[pubKeyHash].state = ValidatorState.REMOVED;

ISSVNetwork(SSV_NETWORK).removeValidator(
publicKey,
operatorIds,
cluster
);

_withdraw(_recipient, _amount, address(this).balance);
emit SSVValidatorRemoved(pubKeyHash, operatorIds);
}

function _withdraw(
address _recipient,
uint256 _withdrawAmount,
uint256 _ethBalance
) internal {
require(_withdrawAmount > 0, "Must withdraw something");
require(_recipient == vaultAddress, "Recipient not Vault");

// Convert any ETH from validator partial withdrawals, exits
// or execution rewards to WETH and do the necessary accounting.
if (_ethBalance > 0) _convertEthToWeth(_ethBalance);

// Transfer WETH to the recipient and do the necessary accounting.
_transferWeth(_withdrawAmount, _recipient);
/// @notice Withdraw ETH funding from this strategy's SSV cluster.
/// @param operatorIds The operator IDs of the SSV Cluster
/// @param amount The amount of ETH to withdraw from the SSV cluster
/// @param cluster The SSV cluster details including the validator count and ETH balance
function withdrawSsvClusterEth(
uint64[] calldata operatorIds,
uint256 amount,
Cluster calldata cluster
) external onlyGovernor {
ISSVNetwork(SSV_NETWORK).withdraw(operatorIds, amount, cluster);

emit Withdrawal(WETH, address(0), _withdrawAmount);
}

/// @notice Transfer all WETH deposits, ETH from validator withdrawals and ETH from
/// execution rewards in this strategy to the vault.
/// This does not withdraw from the validators. That has to be done separately with the
/// `validatorWithdrawal` operation.
function withdrawAll() external override onlyVaultOrGovernor nonReentrant {
uint256 ethBalance = address(this).balance;
uint256 withdrawAmount = IERC20(WETH).balanceOf(address(this)) +
ethBalance;

if (withdrawAmount > 0) {
_withdraw(vaultAddress, withdrawAmount, ethBalance);
if (ethBalance > 0) {
_withdraw(vaultAddress, ethBalance, ethBalance);
}
}

/// @notice Accounts for all the assets managed by this strategy which includes:
/// 1. The current WETH in this strategy contract
/// 2. The last verified ETH balance, total deposits and total validator balances
/// @param _asset Address of WETH asset.
/// @return balance Total value in ETH
function checkBalance(address _asset)
external
view
// slither-disable-end reentrancy-no-eth

function _admitStake(bytes32 pubKeyHash, uint256 depositAmountWei)
internal
override
returns (uint256 balance)
{
require(_asset == WETH, "Unsupported asset");

// Load the last verified balance from the storage
// and add to the latest WETH balance of this strategy.
balance =
lastVerifiedEthBalance +
IWETH9(WETH).balanceOf(address(this));
}

/// @notice Returns bool indicating whether asset is supported by the strategy.
/// @param _asset The address of the WETH token.
function supportsAsset(address _asset) public view override returns (bool) {
return _asset == WETH;
}

/// @notice Does nothing but needed as this function is abstract on InitializableAbstractStrategy
/// @dev Use to be used to approve SSV tokens but that is no longer used by the SSV Network.
function safeApproveAllTokens() public override {}

/**
* @notice We can accept ETH directly to this contract from anyone as it does not impact our accounting
* like it did in the legacy NativeStakingStrategy.
* The new ETH will be accounted for in `checkBalance` after the next snapBalances and verifyBalances txs.
*/
receive() external payable {}

/***************************************
Internal functions
****************************************/

/// @notice is not supported for this strategy as there is no platform token.
function setPTokenAddress(address, address) external pure override {
revert("Unsupported function");
}

/// @notice is not supported for this strategy as there is no platform token.
function removePToken(uint256) external pure override {
revert("Unsupported function");
}
ValidatorState currentState = validator[pubKeyHash].state;
if (
currentState != ValidatorState.REGISTERED &&
currentState != ValidatorState.VERIFIED &&
currentState != ValidatorState.ACTIVE
) {
revert NotRegisteredOrVerified();
}

/// @dev This strategy does not use a platform token like the old Aave and Compound strategies.
function _abstractSetPToken(address _asset, address) internal override {}

/// @dev Consensus rewards are compounded to the validator's balance instead of being
/// swept to this strategy contract.
/// Execution rewards from MEV and tx priority accumulate as ETH in this strategy contract.
/// Withdrawals from validators also accumulate as ETH in this strategy contract.
/// It's too complex to separate the rewards from withdrawals so this function is not implemented.
/// Besides, ETH rewards are not sent to the Dripper any more. The Vault can now regulate
/// the increase in assets.
function _collectRewardTokens() internal pure override {
revert("Unsupported function");
if (currentState == ValidatorState.REGISTERED) {
_recordFirstDeposit(pubKeyHash, depositAmountWei);
}
}
}
Loading
Loading