Skip to content

Commit

Permalink
Native staking changes (#2024)
Browse files Browse the repository at this point in the history
* Added OETH process diagram with functions calls for native staking

* Native Staking Strategy now hold consensus rewards at ETH
FeeAccumulator now holds execution rewards as ETH
Removed WETH immutable from FeeAccumulator
Converted custom errors back to require with string
collect rewards now converts ETH to WETH at harvest
checkBalance is now validators * 32 plus WETH balance from deposits
Renamed beaconChainRewardsWETH to consensusRewards
Fixed bug in stakeETH that was converting all WETH to ETH
  • Loading branch information
naddison36 committed Apr 23, 2024
1 parent 121c7f7 commit 8a64dc8
Show file tree
Hide file tree
Showing 12 changed files with 887 additions and 707 deletions.
51 changes: 18 additions & 33 deletions contracts/contracts/strategies/NativeStaking/FeeAccumulator.sol
Original file line number Diff line number Diff line change
@@ -1,55 +1,40 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Address } from "@openzeppelin/contracts/utils/Address.sol";

import { Governable } from "../../governance/Governable.sol";
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.
* It does NOT include swept ETH from consensus rewards or withdrawals.
* @notice Receives execution rewards which includes tx fees and
* MEV rewards like tx priority and tx ordering.
* It does NOT include swept ETH from beacon chain consensus rewards or full validator withdrawals.
* @author Origin Protocol Inc
*/
contract FeeAccumulator is Governable {
/// @notice The address the WETH is sent to on `collect` which is the Native Staking Strategy
address public immutable COLLECTOR;
/// @notice The address of the Wrapped ETH (WETH) token contract
address public immutable WETH_TOKEN_ADDRESS;

error CallerNotCollector(address caller, address expectedCaller);

// For future use
uint256[50] private __gap;
/// @notice The address of the Native Staking Strategy
address public immutable STRATEGY;

/**
* @param _collector Address of the contract that collects the fees
* @param _weth Address of the Wrapped ETH (WETH) token contract
* @param _strategy Address of the Native Staking Strategy
*/
constructor(address _collector, address _weth) {
COLLECTOR = _collector;
WETH_TOKEN_ADDRESS = _weth;
constructor(address _strategy) {
STRATEGY = _strategy;
}

/**
* @dev Asserts that the caller is the collector
* @notice sends all ETH in this FeeAccumulator contract to the Native Staking Strategy.
* @return eth The amount of execution rewards that were sent to the Native Staking Strategy
*/
function _assertIsCollector() internal view {
if (msg.sender != COLLECTOR) {
revert CallerNotCollector(msg.sender, COLLECTOR);
}
}
function collect() external returns (uint256 eth) {
require(msg.sender == STRATEGY, "Caller is not the Strategy");

/**
* @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 weth) {
_assertIsCollector();
weth = address(this).balance;
if (weth > 0) {
IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: weth }();
IWETH9(WETH_TOKEN_ADDRESS).transfer(COLLECTOR, weth);
eth = address(this).balance;
if (eth > 0) {
// Send the ETH to the Native Staking Strategy
Address.sendValue(payable(STRATEGY), eth);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ contract NativeStakingSSVStrategy is
// For future use
uint256[50] private __gap;

error EmptyRecipient();
error NotWeth();
error InsufficientWethBalance(
uint256 requiredBalance,
uint256 availableBalance
);

/// @param _baseConfig Base strategy config with platformAddress (ERC-4626 Vault contract), eg sfrxETH or sDAI,
/// and vaultAddress (OToken Vault contract), eg VaultProxy or OETHVaultProxy
/// @param _wethAddress Address of the Erc20 WETH Token contract
Expand Down Expand Up @@ -88,73 +81,36 @@ contract NativeStakingSSVStrategy is
);
}

/// @notice return the WETH balance on the contract that can be used to for beacon chain
/// staking - staking on the validators. Because WETH on this contract can be present as
/// a result of deposits and beacon chain rewards this function needs to return only WETH
/// that is present due to deposits.
function getWETHBalanceEligibleForStaking()
public
view
override
returns (uint256 _amount)
{
// if below amount results in a negative number there is a bug with accounting
_amount =
IWETH9(WETH_TOKEN_ADDRESS).balanceOf(address(this)) -
beaconChainRewardWETH;
}

/// @notice Convert accumulated ETH to WETH and send to the Harvester.
/// Only callable by the Harvester.
function collectRewardTokens()
external
virtual
override
onlyHarvester
nonReentrant
{
// collect WETH from fee collector and wrap it into WETH
uint256 wethCollected = FeeAccumulator(FEE_ACCUMULATOR_ADDRESS)
/// @dev Convert accumulated ETH to WETH and send to the Harvester.
function _collectRewardTokens() internal override {
// collect ETH from execution rewards from the fee accumulator
uint256 executionRewards = FeeAccumulator(FEE_ACCUMULATOR_ADDRESS)
.collect();

/* add up the WETH collected from the fee accumulator to beaconChainRewardWETH
* so it can be sent to the harvester in one swoop in the "_collectRewardTokens"
* step.
*/
beaconChainRewardWETH += wethCollected;
_collectRewardTokens();
}
// total ETH rewards to be harvested = execution rewards + consensus rewards
uint256 ethRewards = executionRewards + consensusRewards;

/// @dev Need to override this function since the strategy doesn't allow for all the WETH
/// to be collected. Some might be there as a result of deposit and is waiting for the Registrar
/// to be deposited to the validators.
function _collectRewardTokens() internal override {
uint256 rewardTokenCount = rewardTokenAddresses.length;
for (uint256 i = 0; i < rewardTokenCount; ++i) {
IERC20 rewardToken = IERC20(rewardTokenAddresses[i]);
uint256 balance = rewardToken.balanceOf(address(this));
if (balance > 0) {
if (address(rewardToken) == WETH_TOKEN_ADDRESS) {
if (beaconChainRewardWETH > balance) {
revert InsufficientWethBalance(
beaconChainRewardWETH,
balance
);
}
require(
address(this).balance >= ethRewards,
"insufficient eth balance"
);

if (ethRewards > 0) {
// reset the counter keeping track of beacon chain consensus rewards
consensusRewards = 0;

// only allow for the WETH that is part of beacon chain rewards to be harvested
balance = beaconChainRewardWETH;
// reset the counter keeping track of beacon chain WETH rewards
beaconChainRewardWETH = 0;
}
// Convert ETH rewards to WETH
IWETH9(WETH_TOKEN_ADDRESS).deposit{ value: ethRewards }();

emit RewardTokenCollected(
harvesterAddress,
address(rewardToken),
balance
);
rewardToken.safeTransfer(harvesterAddress, balance);
}
emit RewardTokenCollected(
harvesterAddress,
WETH_TOKEN_ADDRESS,
ethRewards
);
IERC20(WETH_TOKEN_ADDRESS).safeTransfer(
harvesterAddress,
ethRewards
);
}
}

Expand Down Expand Up @@ -218,9 +174,7 @@ contract NativeStakingSSVStrategy is
uint256 _amount
) internal {
require(_amount > 0, "Must withdraw something");
if (_recipient == address(0)) {
revert EmptyRecipient();
}
require(_recipient != address(0), "Must specify recipient");

emit Withdrawal(_asset, address(0), _amount);
IERC20(_asset).safeTransfer(_recipient, _amount);
Expand All @@ -239,7 +193,10 @@ 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 WETH deposits that are still to be staked.
/// This does not include ETH from consensus rewards sitting in this strategy
/// or ETH from MEV rewards in the FeeAccumulator. These rewards are harvested
/// and sent to the Dripper so will eventually be sent to the Vault as WETH.
/// @param _asset Address of weth asset
/// @return balance Total value of (W)ETH
function checkBalance(address _asset)
Expand All @@ -248,13 +205,14 @@ contract NativeStakingSSVStrategy is
override
returns (uint256 balance)
{
if (_asset != WETH_TOKEN_ADDRESS) {
revert NotWeth();
}
require(_asset == WETH_TOKEN_ADDRESS, "Unsupported asset");

balance = activeDepositedValidators * 32 ether;
balance += beaconChainRewardWETH;
balance += FEE_ACCUMULATOR_ADDRESS.balance;
balance =
// add the ETH that has been staked in validators
activeDepositedValidators *
32 ether +
// add the WETH in the strategy from deposits that are still to be staked
IERC20(WETH_TOKEN_ADDRESS).balanceOf(address(this));
}

function pause() external onlyStrategist {
Expand Down Expand Up @@ -293,4 +251,16 @@ contract NativeStakingSSVStrategy is
cluster
);
}

/**
* @notice Only accept ETH from the FeeAccumulator
* @dev don't want to receive donations from anyone else as this will
* mess with the accounting of the consensus rewards and validator full withdrawals
*/
receive() external payable {
require(
msg.sender == FEE_ACCUMULATOR_ADDRESS,
"eth not sent from Fee Accumulator"
);
}
}

0 comments on commit 8a64dc8

Please sign in to comment.