Skip to content
Merged
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
1 change: 1 addition & 0 deletions contracts/0.8.25/utils/Confirmations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ abstract contract Confirmations {

/**
* @dev Emitted when the confirmation expiry is set.
* @param sender msg.sender of the call
* @param oldConfirmExpiry The old confirmation expiry.
* @param newConfirmExpiry The new confirmation expiry.
*/
Expand Down
109 changes: 67 additions & 42 deletions contracts/0.8.25/vaults/LazyOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,12 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
/// @param _quarantinePeriod the quarantine period, seconds
/// @param _maxRewardRatioBP the max reward ratio, basis points
/// @param _maxLidoFeeRatePerSecond the max Lido fee rate per second
function initialize(address _admin, uint256 _quarantinePeriod, uint256 _maxRewardRatioBP, uint256 _maxLidoFeeRatePerSecond) external initializer {
function initialize(
address _admin,
uint256 _quarantinePeriod,
uint256 _maxRewardRatioBP,
uint256 _maxLidoFeeRatePerSecond
) external initializer {
if (_admin == address(0)) revert AdminCannotBeZero();
_grantRole(DEFAULT_ADMIN_ROLE, _admin);

Expand All @@ -161,7 +166,12 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
/// @return refSlot of the report
/// @return treeRoot merkle root of the report
/// @return reportCid IPFS CID for the report JSON file
function latestReportData() external view returns (uint256 timestamp, uint256 refSlot, bytes32 treeRoot, string memory reportCid) {
function latestReportData() external view returns (
uint256 timestamp,
uint256 refSlot,
bytes32 treeRoot,
string memory reportCid
) {
Storage storage $ = _storage();
return ($.vaultsDataTimestamp, $.vaultsDataRefSlot, $.vaultsDataTreeRoot, $.vaultsDataReportCid);
}
Expand Down Expand Up @@ -238,28 +248,6 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
return _vaultInfo(_vault, _vaultHub());
}

function _vaultInfo(address _vault, VaultHub _vh) internal view returns (VaultInfo memory) {
IStakingVault vault = IStakingVault(_vault);
VaultHub.VaultConnection memory connection = _vh.vaultConnection(_vault);
VaultHub.VaultRecord memory record = _vh.vaultRecord(_vault);
return VaultInfo(
_vault,
vault.availableBalance() + vault.stagedBalance(),
record.inOutDelta.currentValue(),
vault.withdrawalCredentials(),
record.liabilityShares,
record.maxLiabilityShares,
_mintableStETH(_vault),
connection.shareLimit,
connection.reserveRatioBP,
connection.forcedRebalanceThresholdBP,
connection.infraFeeBP,
connection.liquidityFeeBP,
connection.reservationFeeBP,
_vh.isPendingDisconnect(_vault)
);
}

/**
* @notice batch method to mass check the validator stages in PredepositGuarantee contract
* @param _pubkeys the array of validator's pubkeys to check
Expand Down Expand Up @@ -305,14 +293,20 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
$.vaultsDataTreeRoot = _vaultsDataTreeRoot;
$.vaultsDataReportCid = _vaultsDataReportCid;

emit VaultsReportDataUpdated(_vaultsDataTimestamp, _vaultsDataRefSlot, _vaultsDataTreeRoot, _vaultsDataReportCid);
emit VaultsReportDataUpdated(
_vaultsDataTimestamp,
_vaultsDataRefSlot,
_vaultsDataTreeRoot,
_vaultsDataReportCid
);
}

/// @notice Permissionless update of the vault data
/// @param _vault the address of the vault
/// @param _totalValue the total value of the vault
/// @param _cumulativeLidoFees the cumulative Lido fees accrued on the vault (nominated in ether)
/// @param _liabilityShares the liabilityShares of the vault
/// @param _liabilityShares the liabilityShares value of the vault (on the vaultsDataRefSlot)
/// @param _maxLiabilityShares the maxLiabilityShares value of the vault (on the vaultsDataRefSlot)
/// @param _proof the proof of the reported data
function updateVaultData(
address _vault,
Expand Down Expand Up @@ -374,37 +368,63 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
delete quarantines[_vault];
}

function _vaultInfo(address _vault, VaultHub _vh) internal view returns (VaultInfo memory) {
IStakingVault vault = IStakingVault(_vault);
VaultHub.VaultConnection memory connection = _vh.vaultConnection(_vault);
VaultHub.VaultRecord memory record = _vh.vaultRecord(_vault);
return VaultInfo(
_vault,
vault.availableBalance() + vault.stagedBalance(),
record.inOutDelta.currentValue(),
vault.withdrawalCredentials(),
record.liabilityShares,
record.maxLiabilityShares,
_mintableStETH(_vault, _vh),
connection.shareLimit,
connection.reserveRatioBP,
connection.forcedRebalanceThresholdBP,
connection.infraFeeBP,
connection.liquidityFeeBP,
connection.reservationFeeBP,
_vh.isPendingDisconnect(_vault)
);
}

/// @notice handle sanity checks for the vault lazy report data
/// @param _vault the address of the vault
/// @param _totalValue the total value of the vault in refSlot
/// @param _reportRefSlot the refSlot of the report
/// @param _reportTimestamp the timestamp of the report
/// @param _cumulativeLidoFees the cumulative Lido fees accrued on the vault (nominated in ether)
/// @param _liabilityShares the liabilityShares value of the vault (on the _reportRefSlot)
/// @param _maxLiabilityShares the maxLiabilityShares value of the vault (on the _reportRefSlot)
/// @return totalValueWithoutQuarantine the smoothed total value of the vault after sanity checks
/// @return inOutDeltaOnRefSlot the inOutDelta in the refSlot
function _handleSanityChecks(
address _vault,
uint256 _totalValue,
uint48 _reportRefSlot,
uint256 _reportRefSlot,
uint256 _reportTimestamp,
uint256 _cumulativeLidoFees,
uint256 _liabilityShares,
uint256 _maxLiabilityShares
) internal returns (uint256 totalValueWithoutQuarantine, int256 inOutDeltaOnRefSlot) {
VaultHub vaultHub = _vaultHub();
VaultHub.VaultRecord memory record = vaultHub.vaultRecord(_vault);
uint48 previousReportTs = record.report.timestamp;

// 0. Check if the report is already fresh enough
if (uint48(_reportTimestamp) <= record.report.timestamp) {
if (uint48(_reportTimestamp) <= previousReportTs) {
revert VaultReportIsFreshEnough();
}

// 1. Calculate inOutDelta in the refSlot
int256 currentInOutDelta = record.inOutDelta.currentValue();
inOutDeltaOnRefSlot = record.inOutDelta.getValueForRefSlot(_reportRefSlot);
inOutDeltaOnRefSlot = record.inOutDelta.getValueForRefSlot(uint48(_reportRefSlot));

// 2. Sanity check for total value increase
totalValueWithoutQuarantine = _processTotalValue(_vault, _totalValue, inOutDeltaOnRefSlot, record);
totalValueWithoutQuarantine = _processTotalValue(
_vault, _totalValue, inOutDeltaOnRefSlot, record, _reportTimestamp);

// 3. Sanity check for dynamic total value underflow
if (int256(totalValueWithoutQuarantine) + currentInOutDelta - inOutDeltaOnRefSlot < 0) {
Expand All @@ -417,7 +437,7 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
revert CumulativeLidoFeesTooLow(_cumulativeLidoFees, previousCumulativeLidoFees);
}

uint256 maxLidoFees = (_reportTimestamp - record.report.timestamp) * uint256(_storage().maxLidoFeeRatePerSecond);
uint256 maxLidoFees = (_reportTimestamp - previousReportTs) * uint256(_storage().maxLidoFeeRatePerSecond);
if (_cumulativeLidoFees - previousCumulativeLidoFees > maxLidoFees) {
revert CumulativeLidoFeesTooLarge(_cumulativeLidoFees - previousCumulativeLidoFees, maxLidoFees);
}
Expand Down Expand Up @@ -468,7 +488,8 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
address _vault,
uint256 _reportedTotalValue,
int256 _inOutDeltaOnRefSlot,
VaultHub.VaultRecord memory record
VaultHub.VaultRecord memory record,
uint256 _reportTimestamp
) internal returns (uint256 totalValueWithoutQuarantine) {
if (_reportedTotalValue > MAX_SANE_TOTAL_VALUE) {
revert TotalValueTooLarge();
Expand All @@ -493,7 +514,7 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
uint256 quarantineThreshold =
onchainTotalValueOnRefSlot * (TOTAL_BASIS_POINTS + $.maxRewardRatioBP) / TOTAL_BASIS_POINTS;
// 3. Determine current quarantine state
QuarantineState currentState = _determineQuarantineState(quarantine, quarantinedValue, $);
QuarantineState currentState = _determineQuarantineState(quarantine, quarantinedValue, _reportTimestamp);


// Execute logic based on current state and conditions ----------------
Expand All @@ -505,7 +526,12 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
return _reportedTotalValue;
} else {
// Transition: NO_QUARANTINE → QUARANTINE_ACTIVE (start new quarantine)
_startNewQuarantine(_vault, quarantine, _reportedTotalValue - onchainTotalValueOnRefSlot, $.vaultsDataTimestamp);
_startNewQuarantine(
_vault,
quarantine,
_reportedTotalValue - onchainTotalValueOnRefSlot,
_reportTimestamp
);
return onchainTotalValueOnRefSlot;
}
} else if (currentState == QuarantineState.QUARANTINE_ACTIVE) {
Expand Down Expand Up @@ -533,7 +559,7 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
} else {
// Transition: QUARANTINE_EXPIRED → QUARANTINE_ACTIVE (release old, start new)
emit QuarantineReleased(_vault, quarantinedValue);
_startNewQuarantine(_vault, quarantine, totalValueIncrease - quarantinedValue, $.vaultsDataTimestamp);
_startNewQuarantine(_vault, quarantine, totalValueIncrease - quarantinedValue, _reportTimestamp);
return onchainTotalValueOnRefSlot + quarantinedValue;
}
}
Expand All @@ -542,24 +568,24 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
function _determineQuarantineState(
Quarantine storage _quarantine,
uint256 _quarantinedValue,
Storage storage $
uint256 _vaultsDataTimestamp
) internal view returns (QuarantineState) {
if (_quarantinedValue == 0) {
return QuarantineState.NO_QUARANTINE;
}

bool isQuarantineExpired = ($.vaultsDataTimestamp - _quarantine.startTimestamp) >= $.quarantinePeriod;
bool isQuarantineExpired = (_vaultsDataTimestamp - _quarantine.startTimestamp) >= _storage().quarantinePeriod;
return isQuarantineExpired ? QuarantineState.QUARANTINE_EXPIRED : QuarantineState.QUARANTINE_ACTIVE;
}

function _startNewQuarantine(
address _vault,
Quarantine storage _quarantine,
uint256 _amountToQuarantine,
uint64 _currentTimestamp
uint256 _currentTimestamp
) internal {
_quarantine.pendingTotalValueIncrease = uint128(_amountToQuarantine);
_quarantine.startTimestamp = _currentTimestamp;
_quarantine.startTimestamp = uint64(_currentTimestamp);
emit QuarantineActivated(_vault, _amountToQuarantine);
}

Expand All @@ -575,9 +601,8 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
emit SanityParamsUpdated(_quarantinePeriod, _maxRewardRatioBP, _maxLidoFeeRatePerSecond);
}

function _mintableStETH(address _vault) internal view returns (uint256) {
VaultHub vaultHub = _vaultHub();
uint256 mintableShares = vaultHub.totalMintingCapacityShares(_vault, 0 /* zero eth delta */);
function _mintableStETH(address _vault, VaultHub _vh) internal view returns (uint256) {
uint256 mintableShares = _vh.totalMintingCapacityShares(_vault, 0 /* zero eth delta */);
return _getPooledEthBySharesRoundUp(mintableShares);
}

Expand Down
5 changes: 0 additions & 5 deletions contracts/0.8.25/vaults/StakingVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -733,11 +733,6 @@ contract StakingVault is IStakingVault, Ownable2StepUpgradeable {
*/
error InsufficientValidatorWithdrawalFee(uint256 _passed, uint256 _required);

/**
* @notice Thrown when the vault is already ossified
*/
error VaultOssified();

/**
* @notice thrown when trying to recover ETH (via EIP-7528 address) using collectERC20
*/
Expand Down
2 changes: 2 additions & 0 deletions contracts/0.8.25/vaults/VaultHub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ contract VaultHub is PausableUntilWithRoles {

ILido public immutable LIDO;
ILidoLocator public immutable LIDO_LOCATOR;
/// @dev it's cached as immutable to save the gas, but it's add some rigidity to the contract structure
/// and will require update of the VaultHub if HashConsensus changes
IHashConsensus public immutable CONSENSUS_CONTRACT;

/// @param _locator Lido Locator contract
Expand Down
18 changes: 10 additions & 8 deletions contracts/0.8.25/vaults/dashboard/NodeOperatorFee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ contract NodeOperatorFee is Permissions {
*/
bytes32 public constant NODE_OPERATOR_PROVE_UNKNOWN_VALIDATOR_ROLE =
keccak256("vaults.NodeOperatorFee.ProveUnknownValidatorsRole");

/**
* @notice If the accrued fee exceeds this BP of the total value, it is considered abnormally high.
* An abnormally high fee can only be disbursed by `DEFAULT_ADMIN_ROLE`.
Expand All @@ -74,7 +74,7 @@ contract NodeOperatorFee is Permissions {
* Since these assumptions are highly conservative, in practice the operator
* would need to disburse even less frequently before approaching the threshold.
*/
uint256 constant internal ABNORMALLY_HIGH_FEE_THRESHOLD_BP = 1_00;
uint256 constant internal ABNORMALLY_HIGH_FEE_THRESHOLD_BP = 1_00;

// ==================== Packed Storage Slot 1 ====================
/**
Expand Down Expand Up @@ -320,7 +320,7 @@ contract NodeOperatorFee is Permissions {
_setSettledGrowth(_newSettledGrowth);
latestCorrectionTimestamp = uint64(block.timestamp);

emit CorrectionTimestampUpdated(latestCorrectionTimestamp);
emit CorrectionTimestampUpdated(block.timestamp);
}

/**
Expand Down Expand Up @@ -350,10 +350,10 @@ contract NodeOperatorFee is Permissions {
function _setFeeRate(uint256 _newFeeRate) internal {
if (_newFeeRate > TOTAL_BASIS_POINTS) revert FeeValueExceed100Percent();

uint16 oldFeeRate = feeRate;
uint16 newFeeRate = _newFeeRate.toUint16();
uint256 oldFeeRate = feeRate;
uint256 newFeeRate = _newFeeRate;

feeRate = newFeeRate;
feeRate = uint16(newFeeRate);

emit FeeRateSet(msg.sender, oldFeeRate, newFeeRate);
}
Expand All @@ -371,13 +371,15 @@ contract NodeOperatorFee is Permissions {

/**
* @dev Emitted when the node operator fee is set.
* @param sender the address of the sender
* @param oldFeeRate The old node operator fee rate.
* @param newFeeRate The new node operator fee rate.
*/
event FeeRateSet(address indexed sender, uint64 oldFeeRate, uint64 newFeeRate);
event FeeRateSet(address indexed sender, uint256 oldFeeRate, uint256 newFeeRate);

/**
* @dev Emitted when the node operator fee is disbursed.
* @param sender the address of the sender
* @param fee the amount of disbursed fee.
*/
event FeeDisbursed(address indexed sender, uint256 fee);
Expand All @@ -401,7 +403,7 @@ contract NodeOperatorFee is Permissions {
* @dev Emitted when the settled growth is corrected.
* @param timestamp new correction timestamp
*/
event CorrectionTimestampUpdated(uint64 timestamp);
event CorrectionTimestampUpdated(uint256 timestamp);

// ==================== Errors ====================

Expand Down
17 changes: 3 additions & 14 deletions contracts/0.8.25/vaults/dashboard/Permissions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,6 @@ abstract contract Permissions is AccessControlConfirmable {
/// @dev 0x25482e7dc9e29f6da5bd70b6d19d17bbf44021da51ba0664a9f430c94a09c674
bytes32 public constant VAULT_CONFIGURATION_ROLE = keccak256("vaults.Permissions.VaultConfiguration");

/**
* @notice Address of the implementation contract
* @dev Used to prevent initialization in the implementation
*/
address private immutable _SELF;

VaultHub public immutable VAULT_HUB;
ILidoLocator public immutable LIDO_LOCATOR;

Expand All @@ -111,7 +105,8 @@ abstract contract Permissions is AccessControlConfirmable {
_requireNotZero(_vaultHub);
_requireNotZero(_lidoLocator);

_SELF = address(this);
initialized = true;

// @dev vaultHub is cached as immutable to save gas for main operations
VAULT_HUB = VaultHub(payable(_vaultHub));
LIDO_LOCATOR = ILidoLocator(_lidoLocator);
Expand All @@ -123,7 +118,6 @@ abstract contract Permissions is AccessControlConfirmable {
*/
modifier initializer() {
if (initialized) revert AlreadyInitialized();
if (address(this) == _SELF) revert NonProxyCallsForbidden();

initialized = true;
_;
Expand Down Expand Up @@ -175,7 +169,7 @@ abstract contract Permissions is AccessControlConfirmable {
* @dev If an account is not a member of a role, doesn't revert, emits no events.
*/
function revokeRoles(RoleAssignment[] calldata _assignments) external {
if (_assignments.length == 0) revert ZeroArgument();
_requireNotZero(_assignments.length);

for (uint256 i = 0; i < _assignments.length; i++) {
revokeRole(_assignments[i].role, _assignments[i].account);
Expand Down Expand Up @@ -373,11 +367,6 @@ abstract contract Permissions is AccessControlConfirmable {
*/
event Initialized();

/**
* @notice Error when direct calls to the implementation are forbidden
*/
error NonProxyCallsForbidden();

/**
* @notice Error when the contract is already initialized.
*/
Expand Down
Loading