Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4f45951
fix: update pause on connect to fix #1477
tamtamchik Sep 26, 2025
1ddf60c
fix: apply resume to manual pause fix #1475
tamtamchik Sep 26, 2025
7f9111f
feat: obligationsShortfallValue
tamtamchik Sep 29, 2025
632baa2
chore: add new event
tamtamchik Sep 29, 2025
b93776a
chore: naming
tamtamchik Sep 29, 2025
5c921dd
feat: draft for stakeLimit update
tamtamchik Sep 30, 2025
3a38c5d
fix(VaultHub): limit connectVault to the owner of the vault
folkyatina Sep 30, 2025
a20b080
chore: introduce a constant for 31 ether
folkyatina Sep 30, 2025
3309c0c
fix: add a SafeCast on internalization
folkyatina Sep 30, 2025
c3c83a0
feat: add vaultInfo function to lazyOracle
dry914 Sep 30, 2025
8f48435
feat: add tests and fix the code
tamtamchik Sep 30, 2025
ca9d5e1
test(stakingLimit): add unit and fuzzing tests
tamtamchik Sep 30, 2025
5f988b1
chore: naming and simplify
tamtamchik Oct 1, 2025
97cbdb0
chore: fix comments
tamtamchik Oct 1, 2025
b921ad5
fix: remove redundant errror
dry914 Oct 1, 2025
cc7ef3d
chore: rename vaultInfo func
dry914 Oct 1, 2025
3d55079
Merge pull request #1490 from lidofinance/feat/view-vault-info
tamtamchik Oct 1, 2025
8cf2746
Merge pull request #1488 from lidofinance/feat/fix-staking-limit
tamtamchik Oct 1, 2025
cb5a960
chore: fixes after review
tamtamchik Oct 1, 2025
06ad2c1
Merge pull request #1487 from lidofinance/feat/expose-shortfall
tamtamchik Oct 1, 2025
b090c27
Merge branch 'feat/audit-5' into feat/fix-pause
tamtamchik Oct 1, 2025
e1c7f7d
chore: review
tamtamchik Oct 1, 2025
f1e00ec
chore: updates after review
tamtamchik Oct 1, 2025
d726f91
fix: integration tests
tamtamchik Oct 1, 2025
06ff85e
docs: more clarity for annual balance sanity check
folkyatina Oct 1, 2025
e2d48cd
Merge pull request #1484 from lidofinance/feat/fix-pause
tamtamchik Oct 1, 2025
ac68c5c
fix(StETH): check uint128 overflow on share mint
folkyatina Oct 1, 2025
bdb71a9
chore: more intuitive constants in UnstructuredStorageExt
folkyatina Oct 1, 2025
d9878f3
chore: optimize VaultHub bytecode
folkyatina Oct 1, 2025
92a32d7
docs(Dashboard): improve comments
folkyatina Oct 2, 2025
3a99ddc
chore: update rights for ET contracts
dry914 Oct 2, 2025
727bbb0
fix(Lido): make staking pause/resume revert if paused/resumed
folkyatina Oct 2, 2025
516f7e8
fix(Lido): simplify the pause check
folkyatina Oct 3, 2025
2ec78d2
Merge pull request #1489 from lidofinance/fix/vaulthub-polish
folkyatina Oct 4, 2025
863eeb8
Merge pull request #1493 from lidofinance/fix/revertable-staking-pause
folkyatina Oct 4, 2025
8d4265f
fix(PDG): fix reentrancy path in PDG
folkyatina Oct 6, 2025
66d902f
chore(PDG): fix indentation
folkyatina Oct 6, 2025
5af1280
fix: upgrade after TW went live on mainnet
arwer13 Oct 6, 2025
4f252fc
chore: merge deployed-mainnet.json from develop
arwer13 Oct 6, 2025
55c7e33
fix: more tidying up for V3VoteScript.sol
arwer13 Oct 6, 2025
6e7aac5
fix: #1476
tamtamchik Oct 6, 2025
294fd95
Merge remote-tracking branch 'origin/fix/upgrade-after-tw' into feat/…
tamtamchik Oct 6, 2025
e66be55
Merge pull request #1496 from lidofinance/fix/upgrade-after-tw
arwer13 Oct 7, 2025
482a944
Merge pull request #1492 from lidofinance/feat/update-et-rights
arwer13 Oct 7, 2025
dd3312d
Merge branch 'feat/audit-5' into feat/fix-1476
tamtamchik Oct 7, 2025
c978a2e
chore: fixes after review
tamtamchik Oct 7, 2025
ed1d5d8
Merge pull request #1495 from lidofinance/fix/pdg-reentrancy
folkyatina Oct 7, 2025
c7320bb
fix: overflow in node operator fee
folkyatina Oct 7, 2025
a9091e2
docs(PDG): more comments
folkyatina Oct 7, 2025
8f8b86a
Merge pull request #1500 from lidofinance/fix/no-fees-overflow
folkyatina Oct 7, 2025
1da30f1
chore: probably better branching
tamtamchik Oct 7, 2025
12acdb5
docs(VaultHub): clarify EIP-7002 withdrawal fee
folkyatina Oct 7, 2025
88ce964
Merge pull request #1498 from lidofinance/feat/fix-1476
TheDZhon Oct 7, 2025
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
7 changes: 4 additions & 3 deletions contracts/0.4.24/Lido.sol
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
*/
function pauseStaking() external {
_auth(STAKING_PAUSE_ROLE);
require(!isStakingPaused(), "ALREADY_PAUSED");

_pauseStaking();
}
Expand All @@ -331,6 +332,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
_auth(STAKING_CONTROL_ROLE);
require(hasInitialized(), "NOT_INITIALIZED");
_whenNotStopped();
require(isStakingPaused(), "ALREADY_RESUMED");

_resumeStaking();
}
Expand Down Expand Up @@ -384,7 +386,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
/**
* @notice Check staking state: whether it's paused or not
*/
function isStakingPaused() external view returns (bool) {
function isStakingPaused() public view returns (bool) {
return STAKING_STATE_POSITION.getStorageStakeLimitStruct().isStakingPaused();
}

Expand Down Expand Up @@ -1141,10 +1143,9 @@ contract Lido is Versioned, StETHPermit, AragonApp {
StakeLimitState.Data memory stakeLimitData = STAKING_STATE_POSITION.getStorageStakeLimitStruct();
if (stakeLimitData.isStakingLimitSet()) {
uint256 newStakeLimit = stakeLimitData.calculateCurrentStakeLimit() + _amount;
uint256 maxStakeLimit = stakeLimitData.maxStakeLimit;

STAKING_STATE_POSITION.setStorageStakeLimitStruct(
stakeLimitData.updatePrevStakeLimit(newStakeLimit > maxStakeLimit ? maxStakeLimit : newStakeLimit)
stakeLimitData.updatePrevStakeLimit(newStakeLimit)
);
}
}
Expand Down
7 changes: 7 additions & 0 deletions contracts/0.4.24/StETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ contract StETH is IERC20, Pausable {
bytes32 internal constant TOTAL_SHARES_POSITION_LOW128 =
0x6038150aecaa250d524370a0fdcdec13f2690e0723eaf277f41d7cae26b359e6;

/**
* @dev Bitmask for high 128 bits of 256-bit slot
*/
uint256 constant internal UINT128_HIGH_MASK = ~uint256(0) << 128;

/**
* @notice An executed shares transfer from `sender` to `recipient`.
*
Expand Down Expand Up @@ -515,6 +520,8 @@ contract StETH is IERC20, Pausable {
require(_recipient != address(this), "MINT_TO_STETH_CONTRACT");

newTotalShares = _getTotalShares().add(_sharesAmount);
require(newTotalShares & UINT128_HIGH_MASK == 0, "SHARES_OVERFLOW");

TOTAL_SHARES_POSITION_LOW128.setLowUint128(newTotalShares);

shares[_recipient] = shares[_recipient].add(_sharesAmount);
Expand Down
50 changes: 40 additions & 10 deletions contracts/0.4.24/lib/StakeLimitUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ library StakeLimitUnstructuredStorage {
library StakeLimitUtils {
/**
* @notice Calculate stake limit for the current block.
* @dev using `_constGasMin` to make gas consumption independent of the current block number
* @dev using `_constGasMin`, `_constGasMax`, `_saturatingSub`, `_constGasLt` to make gas consumption independent
* of the current block number
*/
function calculateCurrentStakeLimit(StakeLimitState.Data memory _data) internal view returns(uint256 limit) {
uint256 stakeLimitIncPerBlock;
Expand All @@ -102,12 +103,11 @@ library StakeLimitUtils {
}

uint256 blocksPassed = block.number - _data.prevStakeBlockNumber;
uint256 projectedLimit = _data.prevStakeLimit + blocksPassed * stakeLimitIncPerBlock;
uint256 change = blocksPassed * stakeLimitIncPerBlock;

limit = _constGasMin(
projectedLimit,
_data.maxStakeLimit
);
limit = _data.prevStakeLimit < _data.maxStakeLimit ?
_constGasMin(_data.prevStakeLimit + change, _data.maxStakeLimit) :
_constGasMax(_saturatingSub(_data.prevStakeLimit, change), _data.maxStakeLimit);
}

/**
Expand Down Expand Up @@ -215,17 +215,47 @@ library StakeLimitUtils {
return _data;
}

/**
* @notice branchless less-than comparison
* @param a first value
* @param b second value
* @return result 1 if a < b, 0 otherwise
*/
function _constGasLt(uint256 a, uint256 b) internal pure returns (uint256 result) {
assembly {
result := lt(a, b)
}
}

/**
* @notice find a minimum of two numbers with a constant gas consumption
* @dev doesn't use branching logic inside
* @param _lhs left hand side value
* @param _rhs right hand side value
*/
function _constGasMin(uint256 _lhs, uint256 _rhs) internal pure returns (uint256 min) {
uint256 lhsIsLess;
assembly {
lhsIsLess := lt(_lhs, _rhs) // lhsIsLess = (_lhs < _rhs) ? 1 : 0
}
uint256 lhsIsLess = _constGasLt(_lhs, _rhs);
min = (_lhs * lhsIsLess) + (_rhs * (1 - lhsIsLess));
}

/**
* @notice find a maximum of two numbers with a constant gas consumption
* @dev doesn't use branching logic inside
* @param _lhs left hand side value
* @param _rhs right hand side value
*/
function _constGasMax(uint256 _lhs, uint256 _rhs) internal pure returns (uint256 max) {
uint256 lhsIsLess = _constGasLt(_lhs, _rhs);
max = (_lhs * (1 - lhsIsLess)) + (_rhs * lhsIsLess);
}

/**
* @notice unsigned saturating subtraction, bounds to zero instead of overflowing
* @param a first value
* @param b second value
*/
function _saturatingSub(uint256 a, uint256 b) internal pure returns (uint256 result) {
uint256 isUnderflow = _constGasLt(a, b);
result = (a - b) * (1 - isUnderflow);
}
}
4 changes: 2 additions & 2 deletions contracts/0.4.24/utils/UnstructuredStorageExt.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ library UnstructuredStorageExt {
using UnstructuredStorage for bytes32;

uint256 constant internal UINT128_LOW_MASK = ~uint128(0);
uint256 constant internal UINT128_HIGH_MASK = UINT128_LOW_MASK << 128;
uint256 constant internal UINT128_HIGH_MASK = ~uint256(0) << 128;
uint256 constant internal UINT160_LOW_MASK = ~uint160(0);
uint256 constant internal UINT96_HIGH_MASK = UINT160_LOW_MASK << 160;
uint256 constant internal UINT96_HIGH_MASK = ~uint256(0) << 160;

function getLowUint128(bytes32 position) internal view returns (uint256) {
return position.getStorageUint256() & UINT128_LOW_MASK;
Expand Down
29 changes: 14 additions & 15 deletions contracts/0.8.25/utils/V3TemporaryAdmin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ contract V3TemporaryAdmin {
* @dev This is the main external function that should be called after deployment
* @param _lidoLocatorImpl The new LidoLocator implementation address
* @param _evmScriptExecutor The EVM script executor address from easyTrack
* @param _vaultHubAdapter The vault hub adapter address from easyTrack
* @param _vaultsAdapter The vaults' adapter address from easyTrack
*/
function completeSetup(address _lidoLocatorImpl, address _evmScriptExecutor, address _vaultHubAdapter) external {
function completeSetup(address _lidoLocatorImpl, address _evmScriptExecutor, address _vaultsAdapter) external {
if (isSetupComplete) revert SetupAlreadyCompleted();
if (_lidoLocatorImpl == address(0)) revert ZeroLidoLocator();
if (_evmScriptExecutor == address(0)) revert ZeroEvmScriptExecutor();
if (_vaultHubAdapter == address(0)) revert ZeroVaultHubAdapter();
if (_vaultsAdapter == address(0)) revert ZeroVaultsAdapter();

isSetupComplete = true;

Expand All @@ -128,19 +128,18 @@ contract V3TemporaryAdmin {

_setupPredepositGuarantee(locator.predepositGuarantee());
_setupLazyOracle(locator.lazyOracle());
_setupOperatorGrid(locator.operatorGrid(), _evmScriptExecutor);
_setupOperatorGrid(locator.operatorGrid(), _evmScriptExecutor, _vaultsAdapter);
_setupBurner(locator.burner(), locator.accounting(), csmAccounting);
_setupVaultHub(locator.vaultHub(), _evmScriptExecutor, _vaultHubAdapter);
_setupVaultHub(locator.vaultHub(), _vaultsAdapter);
}


/**
* @notice Setup VaultHub with all required roles and transfer admin to agent
* @param _vaultHub The VaultHub contract address
* @param _evmScriptExecutor The EVM script executor address
* @param _vaultHubAdapter The vault hub adapter address
* @param _vaultsAdapter The vaults' adapter address
*/
function _setupVaultHub(address _vaultHub, address _evmScriptExecutor, address _vaultHubAdapter) private {
function _setupVaultHub(address _vaultHub, address _vaultsAdapter) private {
// Get roles from the contract
bytes32 pauseRole = IPausableUntil(_vaultHub).PAUSE_ROLE();
bytes32 vaultMasterRole = IVaultHub(_vaultHub).VAULT_MASTER_ROLE();
Expand All @@ -153,11 +152,9 @@ contract V3TemporaryAdmin {
IAccessControl(_vaultHub).grantRole(vaultMasterRole, AGENT);
IAccessControl(_vaultHub).grantRole(redemptionMasterRole, AGENT);

IAccessControl(_vaultHub).grantRole(vaultMasterRole, _vaultHubAdapter);
IAccessControl(_vaultHub).grantRole(validatorExitRole, _vaultHubAdapter);
IAccessControl(_vaultHub).grantRole(badDebtMasterRole, _vaultHubAdapter);

IAccessControl(_vaultHub).grantRole(redemptionMasterRole, _evmScriptExecutor);
IAccessControl(_vaultHub).grantRole(validatorExitRole, _vaultsAdapter);
IAccessControl(_vaultHub).grantRole(badDebtMasterRole, _vaultsAdapter);
IAccessControl(_vaultHub).grantRole(redemptionMasterRole, _vaultsAdapter);

_transferAdminToAgent(_vaultHub);
}
Expand Down Expand Up @@ -186,11 +183,13 @@ contract V3TemporaryAdmin {
* @notice Setup OperatorGrid with required roles and transfer admin to agent
* @param _operatorGrid The OperatorGrid contract address
* @param _evmScriptExecutor The EVM script executor address
* @param _vaultsAdapter The vaults' adapter address
*/
function _setupOperatorGrid(address _operatorGrid, address _evmScriptExecutor) private {
function _setupOperatorGrid(address _operatorGrid, address _evmScriptExecutor, address _vaultsAdapter) private {
bytes32 registryRole = IOperatorGrid(_operatorGrid).REGISTRY_ROLE();
IAccessControl(_operatorGrid).grantRole(registryRole, AGENT);
IAccessControl(_operatorGrid).grantRole(registryRole, _evmScriptExecutor);
IAccessControl(_operatorGrid).grantRole(registryRole, _vaultsAdapter);
_transferAdminToAgent(_operatorGrid);
}

Expand Down Expand Up @@ -222,7 +221,7 @@ contract V3TemporaryAdmin {
error ZeroLidoLocator();
error ZeroStakingRouter();
error ZeroEvmScriptExecutor();
error ZeroVaultHubAdapter();
error ZeroVaultsAdapter();
error CsmModuleNotFound();
error SetupAlreadyCompleted();
}
49 changes: 30 additions & 19 deletions contracts/0.8.25/vaults/LazyOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -226,29 +226,40 @@ contract LazyOracle is ILazyOracle, AccessControlEnumerableUpgradeable {
VaultInfo[] memory batch = new VaultInfo[](batchSize);
for (uint256 i = 0; i < batchSize; i++) {
address vaultAddress = vaultHub.vaultByIndex(_offset + i + 1);
IStakingVault vault = IStakingVault(vaultAddress);
VaultHub.VaultConnection memory connection = vaultHub.vaultConnection(vaultAddress);
VaultHub.VaultRecord memory record = vaultHub.vaultRecord(vaultAddress);
batch[i] = VaultInfo(
vaultAddress,
vault.availableBalance() + vault.stagedBalance(),
record.inOutDelta.currentValue(),
vault.withdrawalCredentials(),
record.liabilityShares,
record.maxLiabilityShares,
_mintableStETH(vaultAddress),
connection.shareLimit,
connection.reserveRatioBP,
connection.forcedRebalanceThresholdBP,
connection.infraFeeBP,
connection.liquidityFeeBP,
connection.reservationFeeBP,
vaultHub.isPendingDisconnect(vaultAddress)
);
batch[i] = _vaultInfo(vaultAddress, vaultHub);
}
return batch;
}

/// @notice returns the vault data info
/// @param _vault the address of the vault
/// @return the vault data info
function vaultInfo(address _vault) external view returns (VaultInfo memory) {
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
4 changes: 2 additions & 2 deletions contracts/0.8.25/vaults/OperatorGrid.sol
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ contract OperatorGrid is AccessControlEnumerableUpgradeable, Confirmable2Address
emit VaultJailStatusUpdated(_vault, _isInJail);
}

/// @notice Get vault limits
/// @notice Get vault's tier limits
/// @param _vault address of the vault
/// @return nodeOperator node operator of the vault
/// @return tierId tier id of the vault
Expand All @@ -684,7 +684,7 @@ contract OperatorGrid is AccessControlEnumerableUpgradeable, Confirmable2Address
/// @return infraFeeBP infra fee of the vault
/// @return liquidityFeeBP liquidity fee of the vault
/// @return reservationFeeBP reservation fee of the vault
function vaultInfo(address _vault)
function vaultTierInfo(address _vault)
external
view
returns (
Expand Down
Loading