@@ -81,7 +81,7 @@ interface IStakingRouter {
81
81
uint256 _maxDepositsCount ,
82
82
uint256 _stakingModuleId ,
83
83
bytes _depositCalldata
84
- ) external payable returns ( uint256 ) ;
84
+ ) external payable ;
85
85
86
86
function getStakingRewardsDistribution ()
87
87
external
@@ -101,6 +101,11 @@ interface IStakingRouter {
101
101
function getTotalFeeE4Precision () external view returns (uint16 totalFee );
102
102
103
103
function getStakingFeeAggregateDistributionE4Precision () external view returns (uint16 modulesFee , uint16 treasuryFee );
104
+
105
+ function getStakingModuleMaxDepositsCount (uint256 _stakingModuleId , uint256 _depositableEther )
106
+ external
107
+ view
108
+ returns (uint256 );
104
109
}
105
110
106
111
interface IWithdrawalQueue {
@@ -160,7 +165,8 @@ contract Lido is Versioned, StETHPermit, AragonApp {
160
165
161
166
uint256 private constant DEPOSIT_SIZE = 32 ether ;
162
167
uint256 public constant TOTAL_BASIS_POINTS = 10000 ;
163
- /// @dev special value for the last finalizable withdrawal request id
168
+ /// @dev special value to not finalize withdrawal requests
169
+ /// see the `_lastFinalizableRequestId` arg for `handleOracleReport()`
164
170
uint256 private constant DONT_FINALIZE_WITHDRAWALS = 0 ;
165
171
166
172
/// @dev storage slot position for the Lido protocol contracts locator
@@ -244,18 +250,25 @@ contract Lido is Versioned, StETHPermit, AragonApp {
244
250
// The `amount` of ether was sent to the deposit_contract.deposit function
245
251
event Unbuffered (uint256 amount );
246
252
247
- // The amount of ETH sent from StakingRouter contract to Lido contract when deposit called
248
- event StakingRouterDepositRemainderReceived (uint256 amount );
249
-
250
253
/**
251
254
* @dev As AragonApp, Lido contract must be initialized with following variables:
252
255
* NB: by default, staking and the whole Lido pool are in paused state
256
+ *
257
+ * The contract's balance must be non-zero to allow initial holder bootstrap.
258
+ *
253
259
* @param _lidoLocator lido locator contract
254
260
* @param _eip712StETH eip712 helper contract for StETH
255
261
*/
256
262
function initialize (address _lidoLocator , address _eip712StETH )
257
- public onlyInit
263
+ public
264
+ payable
265
+ onlyInit
258
266
{
267
+ uint256 amount = _bootstrapInitialHolder ();
268
+ BUFFERED_ETHER_POSITION.setStorageUint256 (amount);
269
+
270
+ emit Submitted (INITIAL_TOKEN_HOLDER, amount, 0 );
271
+
259
272
_initialize_v2 (_lidoLocator, _eip712StETH);
260
273
initialized ();
261
274
}
@@ -284,15 +297,19 @@ contract Lido is Versioned, StETHPermit, AragonApp {
284
297
* @notice A function to finalize upgrade to v2 (from v1). Can be called only once
285
298
* @dev Value "1" in CONTRACT_VERSION_POSITION is skipped due to change in numbering
286
299
*
300
+ * The initial protocol token holder must exist.
301
+ *
287
302
* For more details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md
288
303
*/
289
304
function finalizeUpgrade_v2 (address _lidoLocator , address _eip712StETH ) external {
290
- require (hasInitialized (), "NOT_INITIALIZED " );
291
305
_checkContractVersion (0 );
306
+ require (hasInitialized (), "NOT_INITIALIZED " );
292
307
293
308
require (_lidoLocator != address (0 ), "LIDO_LOCATOR_ZERO_ADDRESS " );
294
309
require (_eip712StETH != address (0 ), "EIP712_STETH_ZERO_ADDRESS " );
295
310
311
+ require (_sharesOf (INITIAL_TOKEN_HOLDER) != 0 , "INITIAL_HOLDER_EXISTS " );
312
+
296
313
_initialize_v2 (_lidoLocator, _eip712StETH);
297
314
}
298
315
@@ -432,7 +449,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
432
449
* accepts payments of any size. Submitted Ethers are stored in Buffer until someone calls
433
450
* deposit() and pushes them to the Ethereum Deposit contract.
434
451
*/
435
- // solhint-disable-next-line
452
+ // solhint-disable-next-line no-complex-fallback
436
453
function () external payable {
437
454
// protection against accidental submissions by calling non-existent function
438
455
require (msg .data .length == 0 , "NON_EMPTY_DATA " );
@@ -472,17 +489,6 @@ contract Lido is Versioned, StETHPermit, AragonApp {
472
489
emit WithdrawalsReceived (msg .value );
473
490
}
474
491
475
- /**
476
- * @notice A payable function for staking router deposits remainder. Can be called only by `StakingRouter`
477
- * @dev We need a dedicated function because funds received by the default payable function
478
- * are treated as a user deposit
479
- */
480
- function receiveStakingRouterDepositRemainder () external payable {
481
- require (msg .sender == getLidoLocator ().stakingRouter ());
482
-
483
- emit StakingRouterDepositRemainderReceived (msg .value );
484
- }
485
-
486
492
/**
487
493
* @notice Stop pool routine operations
488
494
*/
@@ -550,10 +556,9 @@ contract Lido is Versioned, StETHPermit, AragonApp {
550
556
* @param _lastFinalizableRequestId right boundary of requestId range if equals 0, no requests should be finalized
551
557
* @param _simulatedShareRate share rate that was simulated by oracle when the report data created (1e27 precision)
552
558
*
553
- * NB: `_simulatedShareRate` should be calculated by the Oracle daemon
554
- * invoking the method with static call and passing `_lastFinalizableRequestId` == `_simulatedShareRate` == 0
555
- * plugging the returned values to the following formula:
556
- * `_simulatedShareRate = (postTotalPooledEther * 1e27) / postTotalShares`
559
+ * NB: `_simulatedShareRate` should be calculated off-chain by calling the method with `eth_call` JSON-RPC API
560
+ * while passing `_lastFinalizableRequestId` == `_simulatedShareRate` == 0, and plugging the returned values
561
+ * to the following formula: `_simulatedShareRate = (postTotalPooledEther * 1e27) / postTotalShares`
557
562
*
558
563
* @return postTotalPooledEther amount of ether in the protocol after report
559
564
* @return postTotalShares amount of shares in the protocol after report
@@ -677,14 +682,10 @@ contract Lido is Versioned, StETHPermit, AragonApp {
677
682
* @dev Returns depositable ether amount.
678
683
* Takes into account unfinalized stETH required by WithdrawalQueue
679
684
*/
680
- function getDepositableEther () public view returns (uint256 depositableEth ) {
681
- uint256 bufferedEth = _getBufferedEther ();
685
+ function getDepositableEther () public view returns (uint256 ) {
686
+ uint256 bufferedEther = _getBufferedEther ();
682
687
uint256 withdrawalReserve = IWithdrawalQueue (getLidoLocator ().withdrawalQueue ()).unfinalizedStETH ();
683
-
684
- if (bufferedEth > withdrawalReserve) {
685
- bufferedEth -= withdrawalReserve;
686
- depositableEth = bufferedEth.div (DEPOSIT_SIZE).mul (DEPOSIT_SIZE);
687
- }
688
+ return bufferedEther > withdrawalReserve ? bufferedEther - withdrawalReserve : 0 ;
688
689
}
689
690
690
691
/**
@@ -697,36 +698,29 @@ contract Lido is Versioned, StETHPermit, AragonApp {
697
698
ILidoLocator locator = getLidoLocator ();
698
699
699
700
require (msg .sender == locator.depositSecurityModule (), "APP_AUTH_DSM_FAILED " );
700
- require (_stakingModuleId <= uint24 (- 1 ), "STAKING_MODULE_ID_TOO_LARGE " );
701
701
require (canDeposit (), "CAN_NOT_DEPOSIT " );
702
702
703
- uint256 depositableEth = getDepositableEther ();
704
-
705
- if (depositableEth > 0 ) {
706
- /// available ether amount for deposits (multiple of 32eth)
707
- depositableEth = Math256.min (depositableEth, _maxDepositsCount.mul (DEPOSIT_SIZE));
708
-
709
- uint256 unaccountedEth = _getUnaccountedEther ();
710
- /// @dev transfer ether to SR and make deposit at the same time
711
- /// @notice allow zero value of depositableEth, in this case SR will simply transfer the unaccounted ether to Lido contract
712
- uint256 depositsCount = IStakingRouter (locator.stakingRouter ()).deposit.value (depositableEth)(
713
- _maxDepositsCount,
714
- _stakingModuleId,
715
- _depositCalldata
716
- );
717
-
718
- uint256 depositedAmount = depositsCount.mul (DEPOSIT_SIZE);
719
- assert (depositedAmount <= depositableEth);
720
-
721
- if (depositsCount > 0 ) {
722
- uint256 newDepositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256 ().add (depositsCount);
723
- DEPOSITED_VALIDATORS_POSITION.setStorageUint256 (newDepositedValidators);
724
- emit DepositedValidatorsChanged (newDepositedValidators);
725
-
726
- _markAsUnbuffered (depositedAmount);
727
- assert (_getUnaccountedEther () == unaccountedEth);
728
- }
729
- }
703
+ IStakingRouter stakingRouter = IStakingRouter (locator.stakingRouter ());
704
+ uint256 depositsCount = Math256.min (
705
+ _maxDepositsCount,
706
+ stakingRouter.getStakingModuleMaxDepositsCount (_stakingModuleId, getDepositableEther ())
707
+ );
708
+ if (depositsCount == 0 ) return ;
709
+
710
+ uint256 depositsValue = depositsCount.mul (DEPOSIT_SIZE);
711
+ /// @dev firstly update the local state of the contract to prevent a reentrancy attack,
712
+ /// even if the StakingRouter is a trusted contract.
713
+ BUFFERED_ETHER_POSITION.setStorageUint256 (_getBufferedEther ().sub (depositsValue));
714
+ emit Unbuffered (depositsValue);
715
+
716
+ uint256 newDepositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256 ().add (depositsCount);
717
+ DEPOSITED_VALIDATORS_POSITION.setStorageUint256 (newDepositedValidators);
718
+ emit DepositedValidatorsChanged (newDepositedValidators);
719
+
720
+ /// @dev transfer ether to StakingRouter and make a deposit at the same time. All the ether
721
+ /// sent to StakingRouter is counted as deposited. If StakingRouter can't deposit all
722
+ /// passed ether it MUST revert the whole transaction (never happens in normal circumstances)
723
+ stakingRouter.deposit.value (depositsValue)(depositsCount, _stakingModuleId, _depositCalldata);
730
724
}
731
725
732
726
/// DEPRECATED PUBLIC METHODS
@@ -937,14 +931,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
937
931
STAKING_STATE_POSITION.setStorageStakeLimitStruct (stakeLimitData.updatePrevStakeLimit (currentStakeLimit - msg .value ));
938
932
}
939
933
940
- uint256 sharesAmount;
941
- if (_getTotalPooledEther () != 0 && _getTotalShares () != 0 ) {
942
- sharesAmount = getSharesByPooledEth (msg .value );
943
- } else {
944
- // totalPooledEther is 0: for first-ever deposit
945
- // assume that shares correspond to Ether 1-to-1
946
- sharesAmount = msg .value ;
947
- }
934
+ uint256 sharesAmount = getSharesByPooledEth (msg .value );
948
935
949
936
_mintShares (msg .sender , sharesAmount);
950
937
@@ -1095,30 +1082,13 @@ contract Lido is Versioned, StETHPermit, AragonApp {
1095
1082
_emitTransferAfterMintingShares (treasury, treasuryReward);
1096
1083
}
1097
1084
1098
- /**
1099
- * @dev Records a deposit to the deposit_contract.deposit function
1100
- * @param _amount Total amount deposited to the Consensus Layer side
1101
- */
1102
- function _markAsUnbuffered (uint256 _amount ) internal {
1103
- BUFFERED_ETHER_POSITION.setStorageUint256 (_getBufferedEther ().sub (_amount));
1104
-
1105
- emit Unbuffered (_amount);
1106
- }
1107
-
1108
1085
/**
1109
1086
* @dev Gets the amount of Ether temporary buffered on this contract balance
1110
1087
*/
1111
1088
function _getBufferedEther () internal view returns (uint256 ) {
1112
1089
return BUFFERED_ETHER_POSITION.getStorageUint256 ();
1113
1090
}
1114
1091
1115
- /**
1116
- * @dev Gets unaccounted (excess) Ether on this contract balance
1117
- */
1118
- function _getUnaccountedEther () internal view returns (uint256 ) {
1119
- return address (this ).balance.sub (_getBufferedEther ());
1120
- }
1121
-
1122
1092
/// @dev Calculates and returns the total base balance (multiple of 32) of validators in transient state,
1123
1093
/// i.e. submitted to the official Deposit contract but not yet visible in the CL state.
1124
1094
/// @return transient balance in wei (1e-18 Ether)
@@ -1127,7 +1097,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
1127
1097
uint256 clValidators = CL_VALIDATORS_POSITION.getStorageUint256 ();
1128
1098
// clValidators can never be less than deposited ones.
1129
1099
assert (depositedValidators >= clValidators);
1130
- return depositedValidators. sub ( clValidators).mul (DEPOSIT_SIZE);
1100
+ return (depositedValidators - clValidators).mul (DEPOSIT_SIZE);
1131
1101
}
1132
1102
1133
1103
/**
@@ -1203,7 +1173,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
1203
1173
* (i.e., postpone the extra rewards to be applied during the next rounds)
1204
1174
* 5. Invoke finalization of the withdrawal requests
1205
1175
* 6. Distribute protocol fee (treasury & node operators)
1206
- * 7. Burn excess shares (withdrawn stETH at least )
1176
+ * 7. Burn excess shares within the allowed limit (can postpone some shares to be burnt later )
1207
1177
* 8. Complete token rebase by informing observers (emit an event and call the external receivers if any)
1208
1178
* 9. Sanity check for the provided simulated share rate
1209
1179
*/
@@ -1288,8 +1258,9 @@ contract Lido is Versioned, StETHPermit, AragonApp {
1288
1258
);
1289
1259
1290
1260
// Step 7.
1291
- // Burn excess shares (withdrawn stETH at least)
1292
- uint256 burntWithdrawalQueueShares = _burnSharesLimited (
1261
+ // Burn excess shares within the allowed limit (can postpone some shares to be burnt later)
1262
+ // Return actually burnt shares of the current report's finalized withdrawal requests to use in sanity checks
1263
+ uint256 burntCurrentWithdrawalShares = _burnSharesLimited (
1293
1264
IBurner (_contracts.burner),
1294
1265
_contracts.withdrawalQueue,
1295
1266
reportContext.sharesToBurnFromWithdrawalQueue,
@@ -1313,7 +1284,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
1313
1284
postTotalPooledEther,
1314
1285
postTotalShares,
1315
1286
reportContext.etherToLockOnWithdrawalQueue,
1316
- burntWithdrawalQueueShares ,
1287
+ burntCurrentWithdrawalShares ,
1317
1288
_reportedData.simulatedShareRate
1318
1289
);
1319
1290
}
@@ -1377,17 +1348,20 @@ contract Lido is Versioned, StETHPermit, AragonApp {
1377
1348
/*
1378
1349
* @dev Perform burning of `stETH` shares via the dedicated `Burner` contract.
1379
1350
*
1380
- * NB: some of the burning amount can be postponed for the next reports
1381
- * if positive token rebase smoothened.
1351
+ * NB: some of the burning amount can be postponed for the next reports if positive token rebase smoothened.
1352
+ * It's possible that underlying shares of the current oracle report's finalized withdrawals won't be burnt
1353
+ * completely in a single oracle report round due to the provided `_sharesToBurnLimit` limit
1382
1354
*
1383
- * @return burnt shares from withdrawals queue (when some requests finalized)
1355
+ * @return shares actually burnt for the current oracle report's finalized withdrawals
1356
+ * these shares are assigned to be burnt most recently, so the amount can be calculated deducting
1357
+ * `postponedSharesToBurn` shares (if any) after the burn commitment & execution
1384
1358
*/
1385
1359
function _burnSharesLimited (
1386
1360
IBurner _burner ,
1387
1361
address _withdrawalQueue ,
1388
1362
uint256 _sharesToBurnFromWithdrawalQueue ,
1389
1363
uint256 _sharesToBurnLimit
1390
- ) internal returns (uint256 burntWithdrawalsShares ) {
1364
+ ) internal returns (uint256 burntCurrentWithdrawalShares ) {
1391
1365
if (_sharesToBurnFromWithdrawalQueue > 0 ) {
1392
1366
_burner.requestBurnShares (_withdrawalQueue, _sharesToBurnFromWithdrawalQueue);
1393
1367
}
@@ -1403,7 +1377,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
1403
1377
(uint256 coverShares , uint256 nonCoverShares ) = _burner.getSharesRequestedToBurn ();
1404
1378
uint256 postponedSharesToBurn = coverShares.add (nonCoverShares);
1405
1379
1406
- burntWithdrawalsShares =
1380
+ burntCurrentWithdrawalShares =
1407
1381
postponedSharesToBurn < _sharesToBurnFromWithdrawalQueue ?
1408
1382
_sharesToBurnFromWithdrawalQueue - postponedSharesToBurn : 0 ;
1409
1383
}
0 commit comments