Skip to content

Introduce Redeems Buffer#1767

Draft
rkolpakov wants to merge 4 commits intodevelopfrom
feat/redeems-reserve-over-sr-3.0
Draft

Introduce Redeems Buffer#1767
rkolpakov wants to merge 4 commits intodevelopfrom
feat/redeems-reserve-over-sr-3.0

Conversation

@rkolpakov
Copy link
Copy Markdown
Contributor

No description provided.

Comment thread contracts/0.8.25/RedeemsBuffer.sol Dismissed
Comment thread contracts/0.8.25/RedeemsBuffer.sol Fixed
Comment on lines +95 to +120
function redeem(uint256 _stETHAmount, address _ethRecipient) external onlyRole(REDEEMER_ROLE) whenResumed {
if (_stETHAmount == 0) revert ZeroAmount();
if (_ethRecipient == address(0)) revert ZeroRecipient();
if (LIDO.isStopped()) revert LidoStopped();
if (WITHDRAWAL_QUEUE.isBunkerModeActive()) revert BunkerMode();
if (WITHDRAWAL_QUEUE.isPaused()) revert WQPaused();

uint256 sharesAmount = LIDO.getSharesByPooledEth(_stETHAmount);
uint256 etherAmount = LIDO.getPooledEthByShares(sharesAmount);

uint256 redeemedBefore = STORE.getValue(REDEEMED_ETHER_SLOT);
uint256 available = _reserveBalance - redeemedBefore;
if (etherAmount > available) {
revert InsufficientReserve(etherAmount, available);
}

LIDO.transferSharesFrom(msg.sender, address(this), sharesAmount);
BURNER.requestBurnShares(address(this), sharesAmount);

STORE.set(REDEEMED_ETHER_SLOT, uint104(redeemedBefore + etherAmount));

(bool success,) = _ethRecipient.call{value: etherAmount}("");
if (!success) revert ETHTransferFailed(_ethRecipient, etherAmount);

emit Redeemed(msg.sender, _ethRecipient, _stETHAmount, sharesAmount, etherAmount);
}
Comment thread contracts/0.8.25/RedeemsBuffer.sol Fixed
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 16, 2026

badge

Hardhat Unit Tests Coverage Summary

Details
Filename                                                                Stmts    Miss  Cover    Missing
--------------------------------------------------------------------  -------  ------  -------  -----------------------------------------------------------------------------------------------------------
contracts/0.4.24/Lido.sol                                                 346      37  89.31%   663, 728-765, 779-789, 1144-1163, 1200-1202, 1243-1245, 1277-1324
contracts/0.4.24/StETH.sol                                                 80       0  100.00%
contracts/0.4.24/StETHPermit.sol                                           15       0  100.00%
contracts/0.4.24/lib/Packed64x4.sol                                         5       0  100.00%
contracts/0.4.24/lib/SigningKeys.sol                                       36       0  100.00%
contracts/0.4.24/lib/StakeLimitUtils.sol                                   41       0  100.00%
contracts/0.4.24/nos/NodeOperatorsRegistry.sol                            435       0  100.00%
contracts/0.4.24/utils/Pausable.sol                                         9       0  100.00%
contracts/0.4.24/utils/UnstructuredStorageExt.sol                          14       0  100.00%
contracts/0.4.24/utils/Versioned.sol                                        5       0  100.00%
contracts/0.6.12/WstETH.sol                                                17       0  100.00%
contracts/0.8.25/CLValidatorVerifier.sol                                   34       1  97.06%   92
contracts/0.8.25/RedeemsBuffer.sol                                         66       3  95.45%   130, 222-227
contracts/0.8.25/TopUpGateway.sol                                         100       2  98.00%   251, 299
contracts/0.8.25/ValidatorExitDelayVerifier.sol                            75       0  100.00%
contracts/0.8.25/consolidation/ConsolidationBus.sol                        75       0  100.00%
contracts/0.8.25/consolidation/ConsolidationGateway.sol                    75       0  100.00%
contracts/0.8.25/consolidation/ConsolidationMigrator.sol                   65       0  100.00%
contracts/0.8.25/lib/BeaconChainDepositor.sol                              40       4  90.00%   44, 47, 82, 97
contracts/0.8.25/sr/ISRBase.sol                                             0       0  100.00%
contracts/0.8.25/sr/SRLib.sol                                             289      16  94.46%   57, 95-144, 312
contracts/0.8.25/sr/SRStorage.sol                                          13       0  100.00%
contracts/0.8.25/sr/SRTypes.sol                                             0       0  100.00%
contracts/0.8.25/sr/SRUtils.sol                                            13       1  92.31%   87
contracts/0.8.25/sr/StakingRouter.sol                                     263      14  94.68%   70, 374-383, 628-629, 651, 707, 711, 759, 857-862, 1133
contracts/0.8.25/utils/AccessControlConfirmable.sol                         2       0  100.00%
contracts/0.8.25/utils/Confirmable2Addresses.sol                            5       0  100.00%
contracts/0.8.25/utils/Confirmations.sol                                   37       0  100.00%
contracts/0.8.25/utils/PausableUntilWithRoles.sol                           3       0  100.00%
contracts/0.8.25/vaults/LazyOracle.sol                                    134      18  86.57%   203-209, 248, 276-279, 436, 449, 467, 515, 556-558, 650, 658
contracts/0.8.25/vaults/OperatorGrid.sol                                  196       1  99.49%   203
contracts/0.8.25/vaults/PinnedBeaconProxy.sol                               6       0  100.00%
contracts/0.8.25/vaults/StakingVault.sol                                  111      14  87.39%   307-341
contracts/0.8.25/vaults/ValidatorConsolidationRequests.sol                 48       3  93.75%   183, 187, 199
contracts/0.8.25/vaults/VaultFactory.sol                                   34       0  100.00%
contracts/0.8.25/vaults/VaultHub.sol                                      425      76  82.12%   257-266, 281-287, 342-366, 383, 552-553, 595-688, 997-999, 1087-1091, 1147, 1202-1209, 1495-1496, 1511-1521
contracts/0.8.25/vaults/dashboard/Dashboard.sol                           137       8  94.16%   183-201, 327, 636-649
contracts/0.8.25/vaults/dashboard/NodeOperatorFee.sol                      70       0  100.00%
contracts/0.8.25/vaults/dashboard/Permissions.sol                          47       2  95.74%   321-330
contracts/0.8.25/vaults/interfaces/IPinnedBeaconProxy.sol                   0       0  100.00%
contracts/0.8.25/vaults/interfaces/IPredepositGuarantee.sol                 0       0  100.00%
contracts/0.8.25/vaults/interfaces/IStakingVault.sol                        0       0  100.00%
contracts/0.8.25/vaults/interfaces/IVaultFactory.sol                        0       0  100.00%
contracts/0.8.25/vaults/lib/PinnedBeaconUtils.sol                           5       0  100.00%
contracts/0.8.25/vaults/lib/RecoverTokens.sol                               5       0  100.00%
contracts/0.8.25/vaults/lib/RefSlotCache.sol                               36       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/CLProofVerifier.sol           16       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/MeIfNobodyElse.sol             3       0  100.00%
contracts/0.8.25/vaults/predeposit_guarantee/PredepositGuarantee.sol      213      12  94.37%   483-503, 532, 671, 678, 700
contracts/0.8.9/Accounting.sol                                            101       4  96.04%   176-177, 382-383
contracts/0.8.9/Burner.sol                                                 88       0  100.00%
contracts/0.8.9/DepositSecurityModule.sol                                 126       0  100.00%
contracts/0.8.9/EIP712StETH.sol                                            16       0  100.00%
contracts/0.8.9/LidoExecutionLayerRewardsVault.sol                         16       0  100.00%
contracts/0.8.9/LidoLocator.sol                                            29       0  100.00%
contracts/0.8.9/OracleDaemonConfig.sol                                     28       0  100.00%
contracts/0.8.9/TokenRateNotifier.sol                                      36      36  0.00%    35-130
contracts/0.8.9/TriggerableWithdrawalsGateway.sol                          54       1  98.15%   271
contracts/0.8.9/WithdrawalQueue.sol                                        88       0  100.00%
contracts/0.8.9/WithdrawalQueueBase.sol                                   146       0  100.00%
contracts/0.8.9/WithdrawalQueueERC721.sol                                  89       0  100.00%
contracts/0.8.9/WithdrawalVault.sol                                        37       0  100.00%
contracts/0.8.9/WithdrawalVaultEIP7685.sol                                 45       0  100.00%
contracts/0.8.9/lib/ExitLimitUtils.sol                                     35       0  100.00%
contracts/0.8.9/lib/Math.sol                                                4       0  100.00%
contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol                         22       0  100.00%
contracts/0.8.9/lib/UnstructuredRefStorage.sol                              2       0  100.00%
contracts/0.8.9/oracle/AccountingOracle.sol                               197       3  98.48%   441-442, 616
contracts/0.8.9/oracle/BaseOracle.sol                                      89       1  98.88%   401
contracts/0.8.9/oracle/HashConsensus.sol                                  263       1  99.62%   1005
contracts/0.8.9/oracle/ValidatorsExitBus.sol                              240       0  100.00%
contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol                         57       1  98.25%   217
contracts/0.8.9/proxy/OssifiableProxy.sol                                  17       0  100.00%
contracts/0.8.9/proxy/WithdrawalsManagerProxy.sol                          60       0  100.00%
contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol               387       2  99.48%   1310, 1322
contracts/0.8.9/utils/DummyEmptyContract.sol                                0       0  100.00%
contracts/0.8.9/utils/PausableUntil.sol                                    31       0  100.00%
contracts/0.8.9/utils/Versioned.sol                                        11       0  100.00%
contracts/0.8.9/utils/access/AccessControl.sol                             23       0  100.00%
contracts/0.8.9/utils/access/AccessControlEnumerable.sol                    9       0  100.00%
contracts/common/utils/PausableUntil.sol                                   29       0  100.00%
contracts/tooling/AlertingHarness.sol                                      54       1  98.15%   97
contracts/tooling/sepolia/SepoliaDepositAdapter.sol                        21      21  0.00%    55-106
TOTAL                                                                    6094     283  95.36%

Diff against master

Filename                                                            Stmts    Miss  Cover
----------------------------------------------------------------  -------  ------  --------
contracts/0.4.24/Lido.sol                                             +65     +26  -6.78%
contracts/0.8.25/CLValidatorVerifier.sol                              +34      +1  +97.06%
contracts/0.8.25/RedeemsBuffer.sol                                    +66      +3  +95.45%
contracts/0.8.25/TopUpGateway.sol                                    +100      +2  +98.00%
contracts/0.8.25/consolidation/ConsolidationBus.sol                   +75       0  +100.00%
contracts/0.8.25/consolidation/ConsolidationGateway.sol               +75       0  +100.00%
contracts/0.8.25/consolidation/ConsolidationMigrator.sol              +65       0  +100.00%
contracts/0.8.25/lib/BeaconChainDepositor.sol                         +40      +4  +90.00%
contracts/0.8.25/sr/ISRBase.sol                                         0       0  +100.00%
contracts/0.8.25/sr/SRLib.sol                                        +289     +16  +94.46%
contracts/0.8.25/sr/SRStorage.sol                                     +13       0  +100.00%
contracts/0.8.25/sr/SRTypes.sol                                         0       0  +100.00%
contracts/0.8.25/sr/SRUtils.sol                                       +13      +1  +92.31%
contracts/0.8.25/sr/StakingRouter.sol                                +263     +14  +94.68%
contracts/0.8.25/vaults/VaultHub.sol                                   -2       0  -0.08%
contracts/0.8.25/vaults/predeposit_guarantee/CLProofVerifier.sol        0      -1  +6.25%
contracts/0.8.9/Accounting.sol                                         +5      +2  -1.88%
contracts/0.8.9/Burner.sol                                             -4       0  +100.00%
contracts/0.8.9/DepositSecurityModule.sol                              -2       0  +100.00%
contracts/0.8.9/LidoLocator.sol                                        +3       0  +100.00%
contracts/0.8.9/WithdrawalVault.sol                                    +5       0  +100.00%
contracts/0.8.9/WithdrawalVaultEIP7685.sol                            +45       0  +100.00%
contracts/0.8.9/oracle/AccountingOracle.sol                           +23      +3  -1.52%
contracts/0.8.9/oracle/ValidatorsExitBus.sol                         +102     -10  +7.25%
contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol                     +5       0  +0.17%
contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol          +155     -10  +4.65%
TOTAL                                                               +1433     +51  +0.03%

Results for commit: 8f8b960

Minimum allowed coverage is 95%

♻️ This comment has been updated with latest results

@rkolpakov rkolpakov force-pushed the feat/redeems-reserve-over-sr-3.0 branch from 9e8757c to eb28e1a Compare April 16, 2026 15:07
Comment thread contracts/0.8.25/RedeemsBuffer.sol Fixed
Comment thread contracts/0.8.25/RedeemsBuffer.sol Fixed
@rkolpakov rkolpakov force-pushed the feat/redeems-reserve-over-sr-3.0 branch from eb28e1a to 7f95abd Compare April 17, 2026 11:52
Comment on lines +95 to +100
function initialize(address _admin) external initializer {
if (_admin == address(0)) revert AdminCannotBeZero();
__AccessControlEnumerable_init();
LIDO.approve(address(BURNER), type(uint256).max);
_grantRole(DEFAULT_ADMIN_ROLE, _admin);
}
Comment on lines +216 to +223
function recoverStETHShares(address _recipient) external onlyRole(RECOVER_ROLE) {
if (_recipient == address(0)) revert ZeroRecipient();
uint256 shares = LIDO.sharesOf(address(this));
if (shares > 0) {
emit StETHSharesRecovered(msg.sender, shares, _recipient);
LIDO.transferShares(_recipient, shares);
}
}
@rkolpakov rkolpakov force-pushed the feat/redeems-reserve-over-sr-3.0 branch from 7f95abd to 5151aa6 Compare April 17, 2026 15:07
Comment thread contracts/0.4.24/Lido.sol Outdated
*/
struct BufferedEtherAllocation {
uint256 total;
uint256 redeemsReserve;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider adding it as last property of the struct

Comment thread contracts/0.4.24/Lido.sol Outdated
// --- 1. Buffer round-trip: withdraw unredeemed ETH, reconcile bufferedEther ---
address buffer = locator.redeemsBuffer();
if (buffer != address(0)) {
IRedeemsBuffer(buffer).withdrawUnredeemed(_redeemedEther);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nitpick: the naming is a bit misleading here, since withdrawUnredeemed() is called with redeemedEther

Comment thread contracts/0.8.25/RedeemsBuffer.sol Outdated
contract RedeemsBuffer is PausableUntil, AccessControlEnumerableUpgradeable {
using SafeERC20 for IERC20;
using SafeCast for uint256;
bytes32 public constant PAUSE_ROLE = keccak256("RedeemsBuffer.PauseRole");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I’d recommend keeping the previous role naming convention unless there is a specific need to change it. Having the constant name match the keccak256 input that produces the role hash is a convenient convention and worth preserving if possible

Comment thread contracts/0.8.25/RefSlotStore.sol Outdated
* @author Lido
* @notice Generic key-value store that auto-snapshots uint104 values at oracle frame boundaries
*/
contract RefSlotStore is IRefSlotStore, AccessControlEnumerable {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider making this contract expose only the beacon for the current oracle reporting phase. It may be better to move away from the refSlot terminology and use naming aligned with oracle frames instead — something like AccountingFrameClock.getCurrentFrame(). A shared global storage contract that can be used by different callers, where one caller may overwrite values written by another, feels a bit scary

Comment thread contracts/0.8.9/Accounting.sol Outdated
_pre.totalPooledEther - _pre.externalEther, // we need to change the base as shareRate is now calculated on
_pre.totalShares - _pre.externalShares, // internal ether and shares, but inside it's still total
_pre.totalPooledEther - _pre.externalEther - _pre.redeemedEther, // we need to change the base as shareRate is now calculated on
_pre.totalShares - _pre.externalShares - update.redeemedShares, // internal ether and shares, but inside it's still total
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could this line underflow in the case of a negative report and deferred redeemed shares from the previous report?

Comment on lines +251 to +259
function recoverEther(address _recipient) external onlyRole(RECOVER_ROLE) {
if (_recipient == address(0)) revert ZeroRecipient();
uint256 amount = address(this).balance + _redeemedEther.value - _reserveBalance;
if (amount > 0) {
emit EtherRecovered(msg.sender, amount, _recipient);
(bool success,) = _recipient.call{value: amount}("");
if (!success) revert ETHTransferFailed(_recipient, amount);
}
}
Comment thread contracts/0.8.25/RedeemsBuffer.sol Dismissed
Comment on lines +283 to +286
function _currentRefSlot() private view returns (uint256) {
(uint256 refSlot,) = HASH_CONSENSUS.getCurrentFrame();
return refSlot;
}
@rkolpakov rkolpakov force-pushed the feat/redeems-reserve-over-sr-3.0 branch 4 times, most recently from 45ef24a to ba3ee47 Compare April 24, 2026 19:10
@rkolpakov rkolpakov force-pushed the feat/redeems-reserve-over-sr-3.0 branch from ba3ee47 to 8f8b960 Compare April 24, 2026 20:20
Base automatically changed from feat/staking-router-3.0 to develop May 6, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants