Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-4626 compatibility #426

Draft
wants to merge 22 commits into
base: upgrade/1.1.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 39 additions & 32 deletions contracts/Constellation/OperatorDistributor.sol
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// SPDX-License-Identifier: GPL v3

/**
* /*** /*** /****** /** /** /** /** /**
* /**_/ |_ ** /**__ ** | ** | **| ** | ** |__/
* | ** /** /** | ** | ** \__/ /****** /******* /******* /****** /****** | **| ** /****** /****** /** /****** /*******
* /*** |__/|__/ | *** | ** /**__ **| **__ ** /**_____/|_ **_/ /**__ **| **| ** |____ **|_ **_/ | ** /**__ **| **__ **
* | ** | ** | ** | ** \ **| ** \ **| ****** | ** | ********| **| ** /******* | ** | **| ** \ **| ** \ **
* \ ** /** /** | ** | ** **| ** | **| ** | ** \____ ** | ** /* | **_____/| **| ** /**__ ** | ** /* | **| ** | **| ** | **
* | ***|__/|__/*** | ******|| ****** | ** | ** /******* | **** | *******| **| **| ******** | **** | **| ****** | ** | **
* \___/ |___/ \______/ \______/ |__/ |__/|_______/ \___/ \_______/|__/|__/ \_______/ \___/ |__/ \______/ |__/ |__/
*
* A liquid staking protocol extending Rocket Pool.
* Made w/ <3 by {::}
*
* For more information, visit https://nodeset.io
*
* @author Mike Leach (Wander), Nick Steinhilber (NickS), Theodore Clapp (mryamz), Joe Clapis (jcrtp), Huy Nguyen, Andy Rose (Barbalute)
* @custom:security-info https://docs.nodeset.io/nodeset/security-notice
**/
* /*** /*** /****** /** /** /** /** /**
* /**_/ |_ ** /**__ ** | ** | **| ** | ** |__/
* | ** /** /** | ** | ** \__/ /****** /******* /******* /****** /****** | **| ** /****** /****** /** /****** /*******
* /*** |__/|__/ | *** | ** /**__ **| **__ ** /**_____/|_ **_/ /**__ **| **| ** |____ **|_ **_/ | ** /**__ **| **__ **
* | ** | ** | ** | ** \ **| ** \ **| ****** | ** | ********| **| ** /******* | ** | **| ** \ **| ** \ **
* \ ** /** /** | ** | ** **| ** | **| ** | ** \____ ** | ** /* | **_____/| **| ** /**__ ** | ** /* | **| ** | **| ** | **
* | ***|__/|__/*** | ******|| ****** | ** | ** /******* | **** | *******| **| **| ******** | **** | **| ****** | ** | **
* \___/ |___/ \______/ \______/ |__/ |__/|_______/ \___/ \_______/|__/|__/ \_______/ \___/ |__/ \______/ |__/ |__/
*
* A liquid staking protocol extending Rocket Pool.
* Made w/ <3 by {::}
*
* For more information, visit https://nodeset.io
*
* @author Mike Leach (Wander), Nick Steinhilber (NickS), Theodore Clapp (mryamz), Joe Clapis (jcrtp), Huy Nguyen, Andy Rose (Barbalute)
* @custom:security-info https://docs.nodeset.io/nodeset/security-notice
**/

pragma solidity 0.8.17;

Expand Down Expand Up @@ -57,6 +57,7 @@ contract OperatorDistributor is UpgradeableBase {
event RPLStakeRebalanceEnabledChanged(bool isAllowed);

event MinipoolProcessed(address indexed minipool, uint256 ethRewards, bool indexed finalized);
event EthBeaconRewardsReceived(uint256 ethRewards, uint256 noPortion, uint256 treasuryPortion);

event WarningMinipoolNotStaking(
address indexed _minipoolAddress,
Expand All @@ -71,15 +72,21 @@ contract OperatorDistributor is UpgradeableBase {
bool public rplStakeRebalanceEnabled;

function setRplStakeRebalanceEnabled(bool _newValue) external onlyAdmin {
require(rplStakeRebalanceEnabled != _newValue, 'OperatorDistributor: new rplStakeRebalanceEnabled value must be different than existing value');
require(
rplStakeRebalanceEnabled != _newValue,
'OperatorDistributor: new rplStakeRebalanceEnabled value must be different than existing value'
);
emit RPLStakeRebalanceEnabledChanged(_newValue);
rplStakeRebalanceEnabled = _newValue;
}

bool public minipoolProcessingEnabled;

function setMinipoolProcessingEnabled(bool _newValue) external onlyAdmin {
require(minipoolProcessingEnabled != _newValue, 'OperatorDistributor: new minipoolProcessingEnabled value must be different than existing value');
require(
minipoolProcessingEnabled != _newValue,
'OperatorDistributor: new minipoolProcessingEnabled value must be different than existing value'
);
emit MinipoolProcessingEnabledChanged(_newValue);
minipoolProcessingEnabled = _newValue;
}
Expand All @@ -95,7 +102,10 @@ contract OperatorDistributor is UpgradeableBase {
* @param _targetStakeRatio The new target stake ratio to be set.
*/
function setTargetStakeRatio(uint256 _targetStakeRatio) external onlyAdmin {
require(_targetStakeRatio != targetStakeRatio, 'OperatorDistributor: new targetStakeRatio must be different than existing value');
require(
_targetStakeRatio != targetStakeRatio,
'OperatorDistributor: new targetStakeRatio must be different than existing value'
);
emit TargetStakeRatioUpdated(targetStakeRatio, _targetStakeRatio);
targetStakeRatio = _targetStakeRatio;
}
Expand All @@ -109,7 +119,10 @@ contract OperatorDistributor is UpgradeableBase {
* @param _minimumStakeRatio The new minimum stake ratio to be set.
*/
function setMinimumStakeRatio(uint256 _minimumStakeRatio) external onlyAdmin {
require(_minimumStakeRatio != minimumStakeRatio, 'OperatorDistributor: new minimumStakeRatio must be different than existing value');
require(
_minimumStakeRatio != minimumStakeRatio,
'OperatorDistributor: new minimumStakeRatio must be different than existing value'
);
emit MinStakeRatioUpdated(minimumStakeRatio, _minimumStakeRatio);
minimumStakeRatio = _minimumStakeRatio;
}
Expand Down Expand Up @@ -333,8 +346,8 @@ contract OperatorDistributor is UpgradeableBase {
}

this.rebalanceWethVault();
this.rebalanceRplStake(sna.getEthStaked());
this.rebalanceRplVault();
this.rebalanceRplStake(sna.getEthStaked());

emit MinipoolProcessed(address(minipool), rewards, minipool.getFinalised());
}
Expand Down Expand Up @@ -418,10 +431,7 @@ contract OperatorDistributor is UpgradeableBase {
address rocketNodeStakingAddress = _directory.getRocketNodeStakingAddress();
SafeERC20.safeApprove(rpl, rocketNodeStakingAddress, 0);
SafeERC20.safeApprove(rpl, rocketNodeStakingAddress, _amount);
IRocketNodeStaking(rocketNodeStakingAddress).stakeRPLFor(
getDirectory().getSuperNodeAddress(),
_amount
);
IRocketNodeStaking(rocketNodeStakingAddress).stakeRPLFor(getDirectory().getSuperNodeAddress(), _amount);
}

/// @notice Submits a merkle claim to RP on behalf of the SuperNode
Expand Down Expand Up @@ -517,6 +527,7 @@ contract OperatorDistributor is UpgradeableBase {

uint256 xrETHPortion = rewardAmount - treasuryPortion - nodeOperatorPortion;

emit EthBeaconRewardsReceived(rewardAmount, nodeOperatorPortion, treasuryPortion);
this.onIncreaseOracleError(xrETHPortion);
}

Expand Down Expand Up @@ -573,18 +584,14 @@ contract OperatorDistributor is UpgradeableBase {

function transferMerkleClaimToStreamer(uint256 ethAmount, uint256 rplAmount) external onlyProtocol {
address payable mcsAddress = getDirectory().getMerkleClaimStreamerAddress();

if (ethAmount > 0) {
(bool success, ) = mcsAddress.call{value: ethAmount}('');
require(success, 'ETH transfer to MerkleClaimStreamer failed');
}

if (rplAmount > 0) {
SafeERC20.safeTransfer(
IERC20(_directory.getRPLAddress()),
mcsAddress,
rplAmount
);
SafeERC20.safeTransfer(IERC20(_directory.getRPLAddress()), mcsAddress, rplAmount);
}
}
}
91 changes: 67 additions & 24 deletions contracts/Constellation/RPLVault.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL v3

/**
* /*** /*** /****** /** /** /** /** /**
* /**_/ |_ ** /**__ ** | ** | **| ** | ** |__/
* | ** /** /** | ** | ** \__/ /****** /******* /******* /****** /****** | **| ** /****** /****** /** /****** /*******
* /*** /*** /****** /** /** /** /** /**
* /**_/ |_ ** /**__ ** | ** | **| ** | ** |__/
* | ** /** /** | ** | ** \__/ /****** /******* /******* /****** /****** | **| ** /****** /****** /** /****** /*******
* /*** |__/|__/ | *** | ** /**__ **| **__ ** /**_____/|_ **_/ /**__ **| **| ** |____ **|_ **_/ | ** /**__ **| **__ **
* | ** | ** | ** | ** \ **| ** \ **| ****** | ** | ********| **| ** /******* | ** | **| ** \ **| ** \ **
* \ ** /** /** | ** | ** **| ** | **| ** | ** \____ ** | ** /* | **_____/| **| ** /**__ ** | ** /* | **| ** | **| ** | **
Expand Down Expand Up @@ -33,14 +33,17 @@ import './Utils/UpgradeableBase.sol';
import './OperatorDistributor.sol';

import './Utils/PriceFetcher.sol';
import '../Interfaces/IRateProvider.sol';

import 'hardhat/console.sol';

/**
* @title RPLVault
* @author Theodore Clapp, Mike Leach
* @dev An ERC-4626 vault for staking RPL with a single node run by a decentralized operator set.
* @notice These vault shares will increase or decrease in value according to the rewards or penalties applied to the SuperNodeAccount by Rocket Pool.
*/
contract RPLVault is UpgradeableBase, ERC4626Upgradeable {
contract RPLVault is UpgradeableBase, ERC4626Upgradeable, IRateProvider {
using Math for uint256;

event TreasuryFeeChanged(uint256 indexed oldFee, uint256 indexed newFee);
Expand All @@ -54,14 +57,14 @@ contract RPLVault is UpgradeableBase, ERC4626Upgradeable {
uint256 public treasuryFee;

/**
* @notice Sets the liquidity reserve as a percentage of TVL. E.g. if set to 2% (0.02e18), then 2% of the
* @notice Sets the liquidity reserve as a percentage of TVL. E.g. if set to 2% (0.02e18), then 2% of the
* RPL backing xRPL will be reserved for withdrawals. If the reserve is below maximum, it will be refilled before assets are
* put to work with the OperatorDistributor.
*/
uint256 public liquidityReservePercent;

/**
* @notice the minimum percentage of ETH/RPL TVL allowed
* @notice the minimum percentage of ETH/RPL TVL allowed
* @dev this is a simple percentage, because the RPL TVL is calculated using its price in ETH
*/
uint256 public minWethRplRatio;
Expand All @@ -70,7 +73,7 @@ contract RPLVault is UpgradeableBase, ERC4626Upgradeable {

// can the recipient of a deposit be different than the caller? False by default for extra security
bool public differingSenderRecipientEnabled;

/**
* @notice Initializes the vault with necessary parameters and settings.
* @dev This function sets up the vault's token references, fee structures, and various configurations. It's intended to be called once after deployment.
Expand All @@ -90,7 +93,7 @@ contract RPLVault is UpgradeableBase, ERC4626Upgradeable {

/**
* @notice Handles deposits into the vault, ensuring compliance with WETH coverage ratio.
* @dev This function first checks if the WETH coverage ratio after deposit will still be above the threshold,
* @dev This function first checks if the WETH coverage ratio after deposit will still be above the threshold,
* and then continues with the deposit process. The deposited amount is transferred to the OperatorDistributor for utilization.
* This function overrides the `_deposit` function in the parent contract to ensure custom business logic is applied.
* Processes a minipool after depositing, then rebalances the RPL liquidity.
Expand Down Expand Up @@ -135,15 +138,15 @@ contract RPLVault is UpgradeableBase, ERC4626Upgradeable {
uint256 shares
) internal virtual override {
require(caller == receiver, 'caller must be receiver');

OperatorDistributor od = OperatorDistributor(_directory.getOperatorDistributorAddress());
// first process a minipool to give the best chance at actually withdrawing
od.processNextMinipool();

require(IERC20(asset()).balanceOf(address(this)) >= assets, 'Not enough liquidity to withdraw');

super._withdraw(caller, receiver, owner, assets, shares);

od.rebalanceRplVault(); // just in case there are no minipools to process, rebalance anyway
}

Expand Down Expand Up @@ -181,7 +184,7 @@ contract RPLVault is UpgradeableBase, ERC4626Upgradeable {

/**
* @notice Calculates the missing liquidity needed to meet the liquidity reserve.
* @dev This function calculates the current assets needed to hit the liquidity reserve
* @dev This function calculates the current assets needed to hit the liquidity reserve
* based the current total assets of the vault.
* @return The amount of liquidity required.
*/
Expand Down Expand Up @@ -216,19 +219,11 @@ contract RPLVault is UpgradeableBase, ERC4626Upgradeable {
return IERC20(asset()).balanceOf(address(this));
}

/**
* @notice Convenience function for viewing the maximum deposit allowed
*/
function getMaximumDeposit() public view returns (uint256) {
if(minWethRplRatio == 0) return type(uint256).max;
WETHVault wethVault = WETHVault(_directory.getWETHVaultAddress());
if(wethVault.tvlRatioEthRpl(0, false) < minWethRplRatio) return 0;

uint256 tvlRpl = totalAssets();
uint256 tvlEth = WETHVault(getDirectory().getWETHVaultAddress()).totalAssets();
uint256 rplPerEth = PriceFetcher(getDirectory().getPriceFetcherAddress()).getPrice();

return ((tvlEth * rplPerEth) / minWethRplRatio) - tvlRpl;
/// @dev Shortcut for easier defi integration (e.g. Balancer)
/// @return The value of 1 xRPL in terms of RPL
function getRate() public view returns (uint256) {
return convertToAssets(1 ether);
}

/**ADMIN FUNCTIONS */
Expand Down Expand Up @@ -272,7 +267,7 @@ contract RPLVault is UpgradeableBase, ERC4626Upgradeable {
}

/**
* @notice Sets the liquidity reserve as a percentage of TVL. E.g. if set to 2% (0.02e18), then 2% of the
* @notice Sets the liquidity reserve as a percentage of TVL. E.g. if set to 2% (0.02e18), then 2% of the
* RPL backing xRPL will be reserved for withdrawals. If the reserve is below maximum, it will be refilled before assets are
* put to work with the OperatorDistributor.
* @dev This function allows the admin to update the liquidity reserve which determines the amount available for withdrawals.
Expand All @@ -298,4 +293,52 @@ contract RPLVault is UpgradeableBase, ERC4626Upgradeable {
function setDepositsEnabled(bool _newValue) external onlyAdmin {
depositsEnabled = _newValue;
}

function maxDeposit(address receiver) public view override returns (uint256) {
// Check if deposits are enabled
if (!depositsEnabled) return 0;

// Check if the receiver is sanctioned
if (ISanctions(_directory.getSanctionsAddress()).isSanctioned(receiver)) return 0;

if(minWethRplRatio == 0) return type(uint256).max;

WETHVault wethVault = WETHVault(_directory.getWETHVaultAddress());
console.log("!!! maxDeposit 1");

uint256 tvlEth = wethVault.totalAssets();
console.log("!!! maxDeposit 2");

uint256 tvlRpl = totalAssets();
console.log("!!! maxDeposit 3");

uint256 rplPerEth = PriceFetcher(_directory.getPriceFetcherAddress()).getPrice();
console.log("!!! maxDeposit 4");

uint256 rplToEthRatio = (tvlRpl * 1e18) / (tvlEth * rplPerEth);
console.log("!!! maxDeposit", rplToEthRatio, minWethRplRatio, rplToEthRatio >= minWethRplRatio);
// Check if any deposit is allowed based on eth/rpl ratio
if (rplToEthRatio >= minWethRplRatio) return 0;


return ((tvlEth * rplPerEth) / minWethRplRatio) - tvlRpl;
}

// Overriding maxMint to follow the ERC-4626 specification
function maxMint(address receiver) public view override returns (uint256) {
uint256 maxRplDeposit = maxDeposit(receiver);
return convertToShares(maxRplDeposit);
}

// Overriding maxWithdraw to follow the ERC-4626 specification
function maxWithdraw(address owner) public view override returns (uint256) {
uint256 availableLiquidity = IERC20(asset()).balanceOf(address(this));
return availableLiquidity < convertToAssets(balanceOf(owner)) ? availableLiquidity : convertToAssets(balanceOf(owner));
}

// Overriding maxRedeem to follow the ERC-4626 specification
function maxRedeem(address owner) public view override returns (uint256) {
uint256 availableLiquidity = IERC20(asset()).balanceOf(address(this));
return availableLiquidity < balanceOf(owner) ? convertToShares(availableLiquidity) : balanceOf(owner);
}
}
Loading