-
Notifications
You must be signed in to change notification settings - Fork 204
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
feat: stVaults #874
base: develop
Are you sure you want to change the base?
feat: stVaults #874
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slither found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
Hardhat Unit Tests Coverage Summary
Diff against master
Results for commit: 80d2e49 Minimum allowed coverage is β»οΈ This comment has been updated with latest results |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe restore later
import {ILidoLocator} from "../common/interfaces/ILidoLocator.sol"; | ||
import {IBurner} from "../common/interfaces/IBurner.sol"; | ||
import {IPostTokenRebaseReceiver} from "./interfaces/IPostTokenRebaseReceiver.sol"; | ||
import {IStakingRouter} from "./interfaces/IStakingRouter.sol"; | ||
import {IOracleReportSanityChecker} from "./interfaces/IOracleReportSanityChecker.sol"; | ||
import {IWithdrawalQueue} from "./interfaces/IWithdrawalQueue.sol"; | ||
import {ILido} from "./interfaces/ILido.sol"; | ||
import {ReportValues} from "contracts/common/interfaces/ReportValues.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not embedded in the file here? (Lido contract has embedded ones)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the sake of consistency: let's use one way or another
} | ||
|
||
// Distribute protocol fee (treasury & node operators) | ||
if (_update.sharesToMintAsFees > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this name might be misleading, should be named smth 'sharesToMintAsLidoV2ProtocolFees'
contracts/0.8.25/Accounting.sol
Outdated
|
||
_updateVaults( | ||
_report.vaultValues, | ||
_report.netCashFlows, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wdim 'cashflows'
contracts/0.8.25/Accounting.sol
Outdated
} | ||
} | ||
|
||
/// @dev mints treasury rewards |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only for Lido V2 protocol fee though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π
contracts/0.8.25/vaults/VaultHub.sol
Outdated
import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; | ||
import {IHubVault} from "./interfaces/IHubVault.sol"; | ||
import {Math256} from "contracts/common/lib/Math256.sol"; | ||
import {ILido as StETH} from "contracts/0.8.25/interfaces/ILido.sol"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this iface not in common
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should revise iface structure in the future
contracts/0.8.25/vaults/VaultHub.sol
Outdated
/// @notice role that allows to connect vaults to the hub | ||
bytes32 public constant VAULT_MASTER_ROLE = keccak256("Vaults.VaultHub.VaultMasterRole"); | ||
/// @dev basis points base | ||
uint256 internal constant BPS_BASE = 100_00; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
has a different name everywhere outside this contract TOTAL_BASIS_POINT
contracts/0.8.25/vaults/VaultHub.sol
Outdated
/// @dev basis points base | ||
uint256 internal constant BPS_BASE = 100_00; | ||
/// @dev maximum number of vaults that can be connected to the hub | ||
uint256 internal constant MAX_VAULTS_COUNT = 500; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test for reports should be able to digest this count
revert ExternalBalanceCapReached(address(_vault), capVaultBalance, maxExternalBalance); | ||
} | ||
|
||
VaultSocket memory vr = VaultSocket( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VaultSocket memory vr = VaultSocket( | |
VaultSocket memory vsocket = VaultSocket( |
contracts/0.8.25/vaults/VaultHub.sol
Outdated
|
||
/// @notice disconnects a vault from the hub | ||
/// @dev can be called by vaults only | ||
function disconnectVault(address _vault) external { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's add emergency flow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's consider the following cases:
- voluntary disconnect (this one)
- stop new minting
- eject and withdraw
- disconnect forcibly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- disconnect and dust issue
/// @param _reserveRatio minimum Reserve ratio in basis points | ||
/// @param _reserveRatioThreshold reserve ratio that makes possible to force rebalance on the vault (in basis points) | ||
/// @param _treasuryFeeBP treasury fee in basis points | ||
function connectVault( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for permissionless addition:
- there could be a scenario when we adding a vault under slashing
contracts/0.8.25/vaults/VaultHub.sol
Outdated
vaultToDisconnect.rebalance(stethToBurn); | ||
} | ||
|
||
vaultToDisconnect.report(vaultToDisconnect.valuation(), vaultToDisconnect.inOutDelta(), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move report lower to reduce reentrance potential
contracts/0.8.25/vaults/VaultHub.sol
Outdated
/// @notice force rebalance of the vault to have sufficient reserve ratio | ||
/// @param _vault vault address | ||
/// @dev can be used permissionlessly if the vault's min reserve ratio is broken | ||
function forceRebalance(IHubVault _vault) external { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Embed auto-disconnect if resulting valuation is too small
) internal { | ||
_checkAccountingOracleReport(_contracts, _report, _pre, _update); | ||
|
||
uint256 lastWithdrawalRequestToFinalize; |
Check warning
Code scanning / Slither
Uninitialized local variables Medium
contracts/0.8.25/vaults/VaultHub.sol
Outdated
function _calculateLidoFees( | ||
VaultSocket memory _socket, | ||
uint256 _postTotalSharesNoFees, | ||
uint256 _postTotalPooledEther, | ||
uint256 _preTotalShares, | ||
uint256 _preTotalPooledEther | ||
) internal view returns (uint256 treasuryFeeShares) { | ||
IHubVault vault_ = _socket.vault; | ||
|
||
uint256 chargeableValue = Math256.min( | ||
vault_.valuation(), | ||
(_socket.shareLimit * _preTotalPooledEther) / _preTotalShares | ||
); | ||
|
||
// treasury fee is calculated as a share of potential rewards that | ||
// Lido curated validators could earn if vault's ETH was staked in Lido | ||
// itself and minted as stETH shares | ||
// | ||
// treasuryFeeShares = value * lidoGrossAPR * treasuryFeeRate / preShareRate | ||
// lidoGrossAPR = postShareRateWithoutFees / preShareRate - 1 | ||
// = value * (postShareRateWithoutFees / preShareRate - 1) * treasuryFeeRate / preShareRate | ||
|
||
// TODO: optimize potential rewards calculation | ||
uint256 potentialRewards = ((chargeableValue * (_postTotalPooledEther * _preTotalShares)) / | ||
(_postTotalSharesNoFees * _preTotalPooledEther) - | ||
chargeableValue); | ||
uint256 treasuryFee = (potentialRewards * _socket.treasuryFeeBP) / BPS_BASE; | ||
|
||
treasuryFeeShares = (treasuryFee * _preTotalShares) / _preTotalPooledEther; | ||
} |
Check warning
Code scanning / Slither
Divide before multiply Medium
- treasuryFee = (potentialRewards * _socket.treasuryFeeBP) / TOTAL_BASIS_POINTS
- treasuryFeeShares = (treasuryFee * _preTotalShares) / _preTotalPooledEther
...acts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol
Fixed
Show fixed
Hide fixed
...acts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol
Fixed
Show fixed
Hide fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π
contracts/0.8.25/vaults/VaultHub.sol
Outdated
|
||
stETH.mintExternalShares(_recipient, sharesToMint); | ||
|
||
emit MintedStETHOnVault(_vault, _tokens); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to disentangle events for each core operation later π§
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
backward compatibility is needed for Subgraph
contracts/0.8.25/vaults/VaultHub.sol
Outdated
for (uint256 i = 0; i < length; ++i) { | ||
VaultSocket memory socket = $.sockets[i + 1]; | ||
|
||
// if there is no fee in Lido, then no fee in vaults |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// if there is no fee in Lido, then no fee in vaults | |
// if there is no fee in Lido, then no treasury fee in vaults |
contracts/0.8.25/vaults/VaultHub.sol
Outdated
uint256 mintedStETH = (totalMintedShares * _postTotalPooledEther) / _postTotalShares; //TODO: check rounding | ||
lockedEther[i] = (mintedStETH * BPS_BASE) / (BPS_BASE - socket.reserveRatio); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe combine this to improve rounding
} | ||
} | ||
|
||
function _calculateLidoFees( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function _calculateLidoFees( | |
function _calculateTreasuryFees( |
|
||
uint256 chargeableValue = Math256.min( | ||
vault_.valuation(), | ||
(_socket.shareLimit * _preTotalPooledEther) / _preTotalShares |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
won't work for non-mintable vaults
// itself and minted as stETH shares | ||
// | ||
// treasuryFeeShares = value * lidoGrossAPR * treasuryFeeRate / preShareRate | ||
// lidoGrossAPR = postShareRateWithoutFees / preShareRate - 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
???
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
postShareRateWithoutFeesAndExternalEther (post share rate of Lido V2 without fees)
* @notice Rebalances the vault by transferring ether | ||
* @param _ether Amount of ether to rebalance | ||
*/ | ||
function rebalanceVault(uint256 _ether) external payable virtual onlyRole(DEFAULT_ADMIN_ROLE) fundAndProceed { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add views for ether amount required to fund vault back to health. And other related ratio views?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+++
* @notice Burns stETH tokens from the sender backed by the vault | ||
* @param _tokens Amount of tokens to burn | ||
*/ | ||
function burn(uint256 _tokens) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
burnPermit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
burnWstethPermit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be better in delegate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** | ||
* @notice Mints stETH tokens backed by the vault to a recipient. | ||
* @param _recipient Address of the recipient | ||
* @param _tokens Amount of tokens to mint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's add comment about value because of fundAndProceed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe let's make this more explicit, not a modifier (hard to follow)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π π π
// Lido curated validators could earn if vault's ETH was staked in Lido | ||
// itself and minted as stETH shares | ||
// | ||
// treasuryFeeShares = value * lidoGrossAPR * treasuryFeeRate / preShareRate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// treasuryFeeShares = value * lidoGrossAPR * treasuryFeeRate / preShareRate | |
// treasuryFeeShares = chargeableValue * lidoGrossAPR * treasuryFeeRate / (_preTotalPooledEther / _preTotalShares) |
// itself and minted as stETH shares | ||
// | ||
// treasuryFeeShares = value * lidoGrossAPR * treasuryFeeRate / preShareRate | ||
// lidoGrossAPR = postShareRateWithoutFees / preShareRate - 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
postShareRateWithoutFeesAndExternalEther (post share rate of Lido V2 without fees)
} | ||
} | ||
|
||
function _calculateLidoFees( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// @dev impossible to invoke this method under negative rebase
// | ||
// treasuryFeeShares = value * lidoGrossAPR * treasuryFeeRate / preShareRate | ||
// lidoGrossAPR = postShareRateWithoutFees / preShareRate - 1 | ||
// = value * (postShareRateWithoutFees / preShareRate - 1) * treasuryFeeRate / preShareRate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// = value * (postShareRateWithoutFees / preShareRate - 1) * treasuryFeeRate / preShareRate | |
// treasuryFeeShares = value * (postShareRateWithoutFees / preShareRate - 1) * treasuryFeeRate / preShareRate |
chargeableValue); | ||
uint256 treasuryFee = (potentialRewards * _socket.treasuryFeeBP) / BPS_BASE; | ||
|
||
treasuryFeeShares = (treasuryFee * _preTotalShares) / _preTotalPooledEther; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be precision issues
if (msg.value == 0) revert ZeroArgument("msg.value"); | ||
|
||
VaultStorage storage $ = _getVaultStorage(); | ||
$.inOutDelta += SafeCast.toInt128(int256(msg.value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:)
VaultStorage storage $ = _getVaultStorage(); | ||
$.inOutDelta -= SafeCast.toInt128(int256(_ether)); | ||
|
||
(bool success, ) = _recipient.call{value: _ether}(""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a bit scary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe Address.sendValue
* @param _numberOfDeposits Number of 32 ETH deposits to make | ||
* @param _pubkeys Validator public keys | ||
* @param _signatures Validator signatures | ||
* @dev Ensures vault is healthy and handles deposit logistics |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @dev Ensures vault is healthy and handles deposit logistics | |
* @dev Ensures vault is balanced and handles deposit logistics |
* @param _validatorPublicKey Public key of validator to exit | ||
*/ | ||
function requestValidatorExit(bytes calldata _validatorPublicKey) external onlyOwner { | ||
emit ValidatorsExitRequest(msg.sender, _validatorPublicKey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe make it compatible with ValidatorsExitBus and an ejector service
VaultStorage storage $ = _getVaultStorage(); | ||
$.inOutDelta -= SafeCast.toInt128(int256(_ether)); | ||
|
||
emit Withdrawn(msg.sender, msg.sender, _ether); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
emit Withdrawn(msg.sender, msg.sender, _ether); | |
emit Withdrawn(msg.sender, VAULT_HUB, _ether); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or maybe 'Rebalanced'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π
return address(VAULT_HUB); | ||
} | ||
|
||
receive() external payable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This func accepts ether without updating valuation (and inOutDelta
) and it's not possible to withdraw it back.
The more important thing is that while calling rebalance()
it gets used to reduce inOutDelta
allowing to underflow valuation()
$.locked = SafeCast.toUint128(_locked); | ||
|
||
try IReportReceiver(owner()).onReport(_valuation, _inOutDelta, _locked) {} catch (bytes memory reason) { | ||
emit OnReportFailed(address(this), reason); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe rename this event
|
||
event Funded(address indexed sender, uint256 amount); | ||
event Withdrawn(address indexed sender, address indexed recipient, uint256 amount); | ||
event DepositedToBeaconChain(address indexed sender, uint256 deposits, uint256 amount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe should target pectra (array of amounts)
event Locked(uint256 locked); | ||
event Reported(address indexed vault, uint256 valuation, int256 inOutDelta, uint256 locked); | ||
event OnReportFailed(address vault, bytes reason); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here have three different approaches:
- do not include the emitter contract (and it's fine indeed)
- include the address but indexed
- include non-indexed addr
let's use the first approach π§
/** | ||
* @dev Modifier to fund the staking vault if msg.value > 0 | ||
*/ | ||
modifier fundAndProceed() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
modifier fundAndProceed() { | |
modifier prefundable() { |
* @dev Transfers ownership of the staking vault to a new owner | ||
* @param _newOwner Address of the new owner | ||
*/ | ||
function _transferStVaultOwnership(address _newOwner) internal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see the rename proposal above
* @dev Requests the exit of a validator from the staking vault | ||
* @param _validatorPublicKey Public key of the validator to exit | ||
*/ | ||
function _requestValidatorExit(bytes calldata _validatorPublicKey) internal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe plural for keys
|
||
// ==================== Errors ==================== | ||
|
||
/// @notice Error for zero address arguments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// @notice Error for zero address arguments | |
/// @notice Error for zero value arguments |
/// @notice Error when the withdrawable amount is insufficient. | ||
/// @param withdrawable The amount that is withdrawable | ||
/// @param requested The amount requested to withdraw | ||
error InsufficientWithdrawableAmount(uint256 withdrawable, uint256 requested); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should have a view func to show withdrawable amount
for rebalance we also need a view to show what would happen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this error should be moved to Delegation
not being used here at all (it seems)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π π π
// ==================== Constants ==================== | ||
|
||
uint256 private constant BP_BASE = 10000; // Basis points base (100%) | ||
uint256 private constant MAX_FEE = BP_BASE; // Maximum fee in basis points (100%) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TOTAL_BASIS_POINTS
* - vote on ownership transfer | ||
* - vote on performance fee changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe veto instead of approval (ET dynamics)
later
* @notice Role for the Lido DAO. | ||
* This can be the Lido DAO agent, EasyTrack or any other DAO decision-making system. | ||
* Lido DAO can: | ||
* - set the operator role |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure
* Lido DAO can: | ||
* - set the operator role | ||
* - vote on ownership transfer | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about VaultHub.connect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another issue: vault should express a WILL to connect (two-step)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§ π§ π§ LDO staking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these comments might be not relevant to Delegation
* Key master can: | ||
* - deposit validators to the beacon chain | ||
*/ | ||
bytes32 public constant KEY_MASTER_ROLE = keccak256("Vault.Delegation.KeyMasterRole"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BEACON_CHAIN_DEPOSITOR_ROLE
?
function onReport(uint256 _valuation, int256 _inOutDelta, uint256 _locked) external { | ||
if (msg.sender != address(stakingVault)) revert OnlyStVaultCanCallOnReportHook(); | ||
|
||
managementDue += (_valuation * managementFee) / 365 / BP_BASE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timeElapsed
(in case of missing reports e.g.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and 365.25
int256 unlocked = int256(stakingVault.valuation()) - int256(stakingVault.locked()); | ||
uint256 unreserved = unlocked >= 0 ? uint256(unlocked) : 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can query stakingVault.unlocked()
instead
modifier onlyIfVotedBy(bytes32[] memory _committee, uint256 _votingPeriod) { | ||
bytes32 callId = keccak256(msg.data); | ||
uint256 committeeSize = _committee.length; | ||
uint256 votingStart = block.timestamp - _votingPeriod; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint256 votingStart = block.timestamp - _votingPeriod; | |
uint256 votesVaildSince = block.timestamp - _votingPeriod; |
// ==================== Events ==================== | ||
|
||
/// @notice Emitted when a role member votes on a function requiring committee approval. | ||
event RoleMemberVoted(address member, bytes32 role, uint256 timestamp, bytes data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indexed fields?
/// @notice Emitted when a role member votes on a function requiring committee approval. | ||
event RoleMemberVoted(address member, bytes32 role, uint256 timestamp, bytes data); | ||
|
||
// ==================== Errors ==================== |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fees not covered with event changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First lap finished π
|
||
import {IStakingVault} from "./interfaces/IStakingVault.sol"; | ||
|
||
pragma solidity 0.8.25; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π pragma should go before the imports
uint256 managementFee; | ||
uint256 performanceFee; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint256 managementFee; | |
uint256 performanceFee; | |
uint256 managementFeeBP; | |
uint256 performanceFeeBP; |
|
||
pragma solidity 0.8.25; | ||
|
||
interface IDelegation { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why we don't import this interface though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's add the comment that it's intended (to hook up the particular interface strictly)
} | ||
|
||
contract VaultFactory is UpgradeableBeacon { | ||
address public immutable delegationImpl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
address public immutable delegationImpl; | |
address public immutable DELEGATION_IMPL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why it's immutable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should think about disentangling the beacon and factory (from Azat)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@loga4 (the problem is that it's impossible to upgrade the delegation for the factory)
delegation.grantRole(delegation.LIDO_DAO_ROLE(), _lidoAgent); | ||
delegation.grantRole(delegation.MANAGER_ROLE(), _initializationParams.manager); | ||
delegation.grantRole(delegation.OPERATOR_ROLE(), _initializationParams.operator); | ||
delegation.grantRole(delegation.DEFAULT_ADMIN_ROLE(), msg.sender); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scary though, let's add a comment that those roles above have their admins
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
msg.sender
and use input parameters instead to make the deployments automated without pain (suggested defaultAdmin
above to replace msg.sender
)
|
||
delegation.initialize(address(this), address(vault)); | ||
|
||
delegation.grantRole(delegation.LIDO_DAO_ROLE(), _lidoAgent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe LIDO_AGENT
should be immutable for the factory
/// @notice Creates a new StakingVault and Delegation contracts | ||
/// @param _stakingVaultParams The params of vault initialization | ||
/// @param _initializationParams The params of vault initialization | ||
function createVault( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function createVault( | |
function createVaultWithDelegation( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§ let's extract voting modifier to a library or extension for AC
* @notice The term "fee" is used to express the fee percentage as basis points, e.g. 5%, | ||
* while "due" is the actual amount of the fee, e.g. 1 ether | ||
*/ | ||
contract Delegation is Dashboard, IReportReceiver { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contract Delegation is Dashboard, IReportReceiver { | |
contract Delegation is Dashboard, IReportReceiver, AssetRecoverer { |
|
||
emit VaultCreated(address(delegation), address(vault)); | ||
emit DelegationCreated(msg.sender, address(delegation)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π§ role setup has dependencies on the Delegation
internals which is not perfect
β¦o permissions-polish
* @notice Disconnects the staking vault from the vault hub. | ||
*/ | ||
function voluntaryDisconnect() external payable fundAndProceed { | ||
uint256 shares = vaultHub.vaultSocket(address(stakingVault())).sharesMinted; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint256 shares = vaultHub.vaultSocket(address(stakingVault())).sharesMinted; | |
uint256 shares = vaultSocket().sharesMinted; |
# Conflicts: # lib/protocol/helpers/accounting.ts
β¦o permissions-polish
[VAULTS] Polish Permissions contract and other minor improvements
fix(VaultHub): rename mint/burn
# Conflicts: # .env.example # globals.d.ts # hardhat.config.ts # package.json # tasks/verify-contracts.ts
function initialize(address _defaultAdmin, uint256 _confirmExpiry) external virtual { | ||
// reduces gas cost for `mintWsteth` | ||
// invariant: dashboard does not hold stETH on its balance | ||
STETH.approve(address(WSTETH), type(uint256).max); | ||
|
||
_initialize(_defaultAdmin, _confirmExpiry); | ||
} |
Check warning
Code scanning / Slither
Unused return Medium
function initialize(address _defaultAdmin, uint256 _confirmExpiry) external override { | ||
_initialize(_defaultAdmin, _confirmExpiry); | ||
|
||
// the next line implies that the msg.sender is an operator | ||
// however, the msg.sender is the VaultFactory, and the role will be revoked | ||
// at the end of the initialization | ||
_grantRole(NODE_OPERATOR_MANAGER_ROLE, _defaultAdmin); | ||
_setRoleAdmin(NODE_OPERATOR_MANAGER_ROLE, NODE_OPERATOR_MANAGER_ROLE); | ||
_setRoleAdmin(NODE_OPERATOR_FEE_CLAIM_ROLE, NODE_OPERATOR_MANAGER_ROLE); | ||
} |
Check failure
Code scanning / Slither
Unprotected Initialize High
function transferAndBurnShares(address _vault, uint256 _amountOfShares) external { | ||
STETH.transferSharesFrom(msg.sender, address(this), _amountOfShares); | ||
|
||
burnShares(_vault, _amountOfShares); | ||
} |
Check warning
Code scanning / Slither
Unused return Medium
β π§ Under construction π§ β
Staking Vaults
New way of isolated staking, through separate vaults, with optional stETH liquidity