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

[Draft] Vaults UX suggestions #893

Draft
wants to merge 8 commits into
base: feat/vaults
Choose a base branch
from
150 changes: 146 additions & 4 deletions contracts/0.8.25/vaults/Dashboard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,26 @@ pragma solidity 0.8.25;
import {IStakingVault} from "./interfaces/IStakingVault.sol";
import {AccessControlEnumerable} from "@openzeppelin/contracts-v5.0.2/access/extensions/AccessControlEnumerable.sol";
import {IERC20} from "@openzeppelin/contracts-v5.0.2/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts-v5.0.2/token/ERC20/extensions/draft-IERC20Permit.sol";
import {OwnableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/OwnableUpgradeable.sol";
import {VaultHub} from "./VaultHub.sol";

/// @notice Interface defining a Lido liquid staking pool
/// @dev see also [Lido liquid staking pool core contract](https://docs.lido.fi/contracts/lido)
interface IStETH is IERC20, IERC20Permit {
function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256)
}

interface IWeth is IERC20 {
function withdraw(uint) external;
function deposit() external payable;
}

interface IWstETH is IERC20, IERC20Permit {
function wrap(uint256) external returns (uint256);
function unwrap(uint256) external returns (uint256);
}

/**
* @title Dashboard
* @notice This contract is meant to be used as the owner of `StakingVault`.
Expand All @@ -27,23 +44,35 @@ contract Dashboard is AccessControlEnumerable {
bool public isInitialized;

/// @notice The stETH token contract
IERC20 public immutable stETH;
IStETH public immutable stETH;

/// @notice The underlying `StakingVault` contract
IStakingVault public stakingVault;

/// @notice The `VaultHub` contract
VaultHub public vaultHub;

/// @notice The wrapped ether token contract
IWeth public weth;

/// @notice The wrapped staked ether token contract
IWstETH public wstETH;

/**
* @notice Constructor sets the stETH token address and the implementation contract address.
* @param _stETH Address of the stETH token contract.
* @param _weth Address of the weth token contract.
* @param _wstETH Address of the wstETH token contract.
*/
constructor(address _stETH) {
constructor(address _stETH, address _weth, address _wstETH) {
if (_stETH == address(0)) revert ZeroArgument("_stETH");
if (_weth == address(0)) revert ZeroArgument("_weth");
if (_wstETH == address(0)) revert ZeroArgument("_wstETH");

_SELF = address(this);
stETH = IERC20(_stETH);
weth = IWeth(_weth);
wstETH = IWstETH(_wstETH);
}

/**
Expand Down Expand Up @@ -126,6 +155,49 @@ contract Dashboard is AccessControlEnumerable {
return vaultSocket().treasuryFeeBP;
}

/**
* @notice Returns the maximum number of stETH shares that can be minted on the vault.
* @return The maximum number of stETH shares as a uint256.
*/
function maxMintableShares() external view returns (uint256) {
return vaultHub._maxMintableShares(address(stakingVault), vaultSocket().reserveRatio);
}

/**
* @notice Returns the maximum number of stETH shares that can be minted.
* @return The maximum number of stETH shares that can be minted.
*/
function canMint() external view returns (uint256) {

uint256 maxMintableShares = maxMintableShares();
uint256 sharesMinted = vaultSocket().sharesMinted;

return maxMintableShares - sharesMinted;
}

/**
* @notice Returns the maximum number of stETH that can be minted for deposited ether.
* @param _ether The amount of ether to check.
* @return the maximum number of stETH that can be minted by ether
*/
function canMintByEther(uint256 _ether) external view returns (uint256) {
if (_ether == 0) return 0;

uint256 maxMintableShares = maxMintableShares();
uint256 sharesMinted = vaultSocket().sharesMinted;
uint256 sharesToMint = stETH.getSharesByPooledEth(_ether);

return sharesMinted + sharesToMint > maxMintableShares ? maxMintableShares - sharesMinted : sharesToMint;
}

/**
* @notice Returns the amount of ether that can be withdrawn from the staking vault.
* @return The amount of ether that can be withdrawn.
*/
function canWithdraw() external view returns (uint256) {
return Math256.min(address(stakingVault).balance, stakingVault.unlocked());
}

// ==================== Vault Management Functions ====================

/**
Expand All @@ -150,6 +222,16 @@ contract Dashboard is AccessControlEnumerable {
_fund();
}

/**
* @notice Funds the staking vault with wrapped ether. Approvals for the passed amounts should be done before.
* @param _wethAmount Amount of wrapped ether to fund the staking vault with
*/
function fundByWeth(uint256 _wethAmount) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing transferFrom?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

weth.transferFrom(msg.sender, address(this), _wethAmount);
weth.withdraw(_wethAmount);
_fund{value: _wethAmount}();
}

/**
* @notice Withdraws ether from the staking vault to a recipient
* @param _recipient Address of the recipient
Expand All @@ -159,6 +241,17 @@ contract Dashboard is AccessControlEnumerable {
_withdraw(_recipient, _ether);
}

/**
* @notice Withdraws stETH tokens from the staking vault to wrapped ether. Approvals for the passed amounts should be done before.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, didn't get what it should do. RN, ether is still on the dashboard contract. Should it be transferred to msg.sender or some other recepient?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@folkyatina The idea is to send to the fundByWeth method the amount of WETH that needs to be "unwrap" to the ETH and then fund Vault
WETH withdraw method https://github.com/gnosis/canonical-weth/blob/master/contracts/WETH9.sol#L38

Now I understand that it seems that this code will not work correctly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

* @param _recipient Address of the recipient
* @param _ether Amount of ether to withdraw
*/
function withdrawToWeth(address _recipient, uint256 _ether) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_withdraw(address(this), _ether);
weth.deposit{value: _ether}();
weth.transfer(_recipient, _ether);
}

/**
* @notice Requests the exit of a validator from the staking vault
* @param _validatorPublicKey Public key of the validator to exit
Expand Down Expand Up @@ -194,13 +287,62 @@ contract Dashboard is AccessControlEnumerable {
}

/**
* @notice Burns stETH tokens from the sender backed by the vault
* @param _tokens Amount of tokens to burn
* @notice Mints wstETH tokens backed by the vault to a recipient. Approvals for the passed amounts should be done before.
* @param _recipient Address of the recipient
* @param _tokens Amount of tokens to mint
*/
function mintWstETH(address _recipient, uint256 _tokens) external payable virtual onlyRole(DEFAULT_ADMIN_ROLE) fundAndProceed {
_mint(address(this), _tokens);

stETH.approve(address(wstETH), _tokens);
uint256 wstETHAmount = wstETH.wrap(_tokens);
wstETH.transfer(_recipient, wstETHAmount);
}

/**
* @notice Burns stETH tokens from the sender backed by the vault. Approvals for the passed amounts should be done before.
* @param _tokens Amount of stETH tokens to burn
*/
function burn(uint256 _tokens) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_burn(_tokens);
}

/**
* @notice Burns wstETH tokens from the sender backed by the vault. Approvals for the passed amounts should be done before.
* @param _tokens Amount of wstETH tokens to burn
*/
function burnWstETH(uint256 _tokens) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
wstETH.transferFrom(msg.sender, address(this), _tokens);
stETH.approve(address(wstETH), _tokens);

uint256 stETHAmount = wstETH.unwrap(_tokens);
_burn(stETHAmount);
}

/**
* @notice Burns stETH tokens from the sender backed by the vault using EIP-2612 Permit.
* @param _tokens Amount of stETH tokens to burn
* @param _permit data required for the stETH.permit() method to set the allowance
*/
function burnWithPermit(uint256 _tokens, PermitInput calldata _permit) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
DiRaiks marked this conversation as resolved.
Show resolved Hide resolved
stETH.permit(msg.sender, address(this), _permit.value, _permit.deadline, _permit.v, _permit.r, _permit.s);
_burn(_tokens);
}

/**
* @notice Burns wstETH tokens from the sender backed by the vault using EIP-2612 Permit.
* @param _tokens Amount of wstETH tokens to burn
* @param _wstETHPermit data required for the wstETH.permit() method to set the allowance
*/
function burnWstETHWithPermit(uint256 _tokens, PermitInput calldata _wstETHPermit) external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
wstETH.permit(msg.sender, address(this), _wstETHPermit.value, _wstETHPermit.deadline, _wstETHPermit.v, _wstETHPermit.r, _wstETHPermit.s);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs check for allowance to prevent ddos. See updated WQ contract.


wstETH.transferFrom(msg.sender, address(this), _tokens);
stETH.approve(address(wstETH), _tokens);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need approve for unwrap.

uint256 stETHAmount = wstETH.unwrap(_tokens);
_burn(stETHAmount);
}

/**
* @notice Rebalances the vault by transferring ether
* @param _ether Amount of ether to rebalance
Expand Down
Loading