Skip to content

Commit e45c4d6

Browse files
authored
Merge pull request #732 from lidofinance/fix/shapella-upgrade-from-rc1-to-rc2
Fix: shapella upgrade from rc1 to rc2
2 parents feafec4 + 9496e61 commit e45c4d6

33 files changed

+1360
-626
lines changed

contracts/0.4.24/Lido.sol

+4-4
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ interface IWithdrawalQueue {
122122
view
123123
returns (uint256 ethToLock, uint256 sharesToBurn);
124124

125-
function finalize(uint256[] _batches, uint256 _maxShareRate) external payable;
125+
function finalize(uint256 _lastIdToFinalize, uint256 _maxShareRate) external payable;
126126

127127
function isPaused() external view returns (bool);
128128

@@ -282,12 +282,12 @@ contract Lido is Versioned, StETHPermit, AragonApp {
282282
LIDO_LOCATOR_POSITION.setStorageAddress(_lidoLocator);
283283
_initializeEIP712StETH(_eip712StETH);
284284

285-
// set unlimited allowance for burner from withdrawal queue
285+
// set infinite allowance for burner from withdrawal queue
286286
// to burn finalized requests' shares
287287
_approve(
288288
ILidoLocator(_lidoLocator).withdrawalQueue(),
289289
ILidoLocator(_lidoLocator).burner(),
290-
~uint256(0)
290+
INFINITE_ALLOWANCE
291291
);
292292

293293
emit LidoLocatorSet(_lidoLocator);
@@ -851,7 +851,7 @@ contract Lido is Versioned, StETHPermit, AragonApp {
851851
if (_etherToLockOnWithdrawalQueue > 0) {
852852
IWithdrawalQueue withdrawalQueue = IWithdrawalQueue(_contracts.withdrawalQueue);
853853
withdrawalQueue.finalize.value(_etherToLockOnWithdrawalQueue)(
854-
_withdrawalFinalizationBatches,
854+
_withdrawalFinalizationBatches[_withdrawalFinalizationBatches.length - 1],
855855
_simulatedShareRate
856856
);
857857
}

contracts/0.4.24/StETH.sol

+21-10
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ contract StETH is IERC20, Pausable {
5151
using UnstructuredStorage for bytes32;
5252

5353
address constant internal INITIAL_TOKEN_HOLDER = 0xdead;
54+
uint256 constant internal INFINITE_ALLOWANCE = ~uint256(0);
5455

5556
/**
5657
* @dev StETH balances are dynamic and are calculated based on the accounts' shares
@@ -234,11 +235,8 @@ contract StETH is IERC20, Pausable {
234235
* @dev The `_amount` argument is the amount of tokens, not shares.
235236
*/
236237
function transferFrom(address _sender, address _recipient, uint256 _amount) external returns (bool) {
237-
uint256 currentAllowance = allowances[_sender][msg.sender];
238-
require(currentAllowance >= _amount, "ALLOWANCE_EXCEEDED");
239-
238+
_spendAllowance(_sender, msg.sender, _amount);
240239
_transfer(_sender, _recipient, _amount);
241-
_approve(_sender, msg.sender, currentAllowance.sub(_amount));
242240
return true;
243241
}
244242

@@ -247,7 +245,7 @@ contract StETH is IERC20, Pausable {
247245
*
248246
* This is an alternative to `approve` that can be used as a mitigation for
249247
* problems described in:
250-
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42
248+
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/b709eae01d1da91902d06ace340df6b324e6f049/contracts/token/ERC20/IERC20.sol#L57
251249
* Emits an `Approval` event indicating the updated allowance.
252250
*
253251
* Requirements:
@@ -264,7 +262,7 @@ contract StETH is IERC20, Pausable {
264262
*
265263
* This is an alternative to `approve` that can be used as a mitigation for
266264
* problems described in:
267-
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol#L42
265+
* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/b709eae01d1da91902d06ace340df6b324e6f049/contracts/token/ERC20/IERC20.sol#L57
268266
* Emits an `Approval` event indicating the updated allowance.
269267
*
270268
* Requirements:
@@ -355,12 +353,9 @@ contract StETH is IERC20, Pausable {
355353
function transferSharesFrom(
356354
address _sender, address _recipient, uint256 _sharesAmount
357355
) external returns (uint256) {
358-
uint256 currentAllowance = allowances[_sender][msg.sender];
359356
uint256 tokensAmount = getPooledEthByShares(_sharesAmount);
360-
require(currentAllowance >= tokensAmount, "ALLOWANCE_EXCEEDED");
361-
357+
_spendAllowance(_sender, msg.sender, tokensAmount);
362358
_transferShares(_sender, _recipient, _sharesAmount);
363-
_approve(_sender, msg.sender, currentAllowance.sub(tokensAmount));
364359
_emitTransferEvents(_sender, _recipient, tokensAmount, _sharesAmount);
365360
return tokensAmount;
366361
}
@@ -403,6 +398,22 @@ contract StETH is IERC20, Pausable {
403398
emit Approval(_owner, _spender, _amount);
404399
}
405400

401+
/**
402+
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
403+
*
404+
* Does not update the allowance amount in case of infinite allowance.
405+
* Revert if not enough allowance is available.
406+
*
407+
* Might emit an {Approval} event.
408+
*/
409+
function _spendAllowance(address _owner, address _spender, uint256 _amount) internal {
410+
uint256 currentAllowance = allowances[_owner][_spender];
411+
if (currentAllowance != INFINITE_ALLOWANCE) {
412+
require(currentAllowance >= _amount, "ALLOWANCE_EXCEEDED");
413+
_approve(_owner, _spender, currentAllowance - _amount);
414+
}
415+
}
416+
406417
/**
407418
* @return the total amount of shares in existence.
408419
*/

contracts/0.4.24/nos/NodeOperatorsRegistry.sol

+15-8
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned {
8585
// SigningKeysStats
8686
/// @dev Operator's max validator keys count approved for deposit by the DAO
8787
uint8 internal constant TOTAL_VETTED_KEYS_COUNT_OFFSET = 0;
88-
/// @dev Number of keys in the EXITED state for this operator for all time
88+
/// @dev Number of keys in the EXITED state of this operator for all time
8989
uint8 internal constant TOTAL_EXITED_KEYS_COUNT_OFFSET = 1;
9090
/// @dev Total number of keys of this operator for all time
9191
uint8 internal constant TOTAL_KEYS_COUNT_OFFSET = 2;
@@ -116,11 +116,11 @@ contract NodeOperatorsRegistry is AragonApp, Versioned {
116116

117117
// Summary SigningKeysStats
118118
uint8 internal constant SUMMARY_MAX_VALIDATORS_COUNT_OFFSET = 0;
119-
/// @dev Number of keys in the EXITED state for this operator for all time
119+
/// @dev Number of keys of all operators which were in the EXITED state for all time
120120
uint8 internal constant SUMMARY_EXITED_KEYS_COUNT_OFFSET = 1;
121-
/// @dev Total number of keys of this operator for all time
121+
/// @dev Total number of keys of all operators for all time
122122
uint8 internal constant SUMMARY_TOTAL_KEYS_COUNT_OFFSET = 2;
123-
/// @dev Number of keys of this operator which were in DEPOSITED state for all time
123+
/// @dev Number of keys of all operators which were in the DEPOSITED state for all time
124124
uint8 internal constant SUMMARY_DEPOSITED_KEYS_COUNT_OFFSET = 3;
125125

126126
//
@@ -150,7 +150,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned {
150150
// bytes32 internal constant TYPE_POSITION = keccak256("lido.NodeOperatorsRegistry.type");
151151
bytes32 internal constant TYPE_POSITION = 0xbacf4236659a602d72c631ba0b0d67ec320aaf523f3ae3590d7faee4f42351d0;
152152

153-
// bytes32 internal constant TYPE_POSITION = keccak256("lido.NodeOperatorsRegistry.stuckPenaltyDelay");
153+
// bytes32 internal constant STUCK_PENALTY_DELAY_POSITION = keccak256("lido.NodeOperatorsRegistry.stuckPenaltyDelay");
154154
bytes32 internal constant STUCK_PENALTY_DELAY_POSITION = 0x8e3a1f3826a82c1116044b334cae49f3c3d12c3866a1c4b18af461e12e58a18e;
155155

156156
//
@@ -286,7 +286,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned {
286286
/// @return id a unique key of the added operator
287287
function addNodeOperator(string _name, address _rewardAddress) external returns (uint256 id) {
288288
_onlyValidNodeOperatorName(_name);
289-
_onlyNonZeroAddress(_rewardAddress);
289+
_onlyValidRewardAddress(_rewardAddress);
290290
_auth(MANAGE_NODE_OPERATOR_ROLE);
291291

292292
id = getNodeOperatorsCount();
@@ -370,7 +370,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned {
370370
/// @param _nodeOperatorId Node operator id to set reward address for
371371
/// @param _rewardAddress Execution layer Ethereum address to set as reward address
372372
function setNodeOperatorRewardAddress(uint256 _nodeOperatorId, address _rewardAddress) external {
373-
_onlyNonZeroAddress(_rewardAddress);
373+
_onlyValidRewardAddress(_rewardAddress);
374374
_onlyExistedNodeOperator(_nodeOperatorId);
375375
_auth(MANAGE_NODE_OPERATOR_ROLE);
376376

@@ -383,7 +383,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned {
383383
/// @dev Current implementation preserves invariant: depositedSigningKeysCount <= vettedSigningKeysCount <= totalSigningKeysCount.
384384
/// If _vettedSigningKeysCount out of range [depositedSigningKeysCount, totalSigningKeysCount], the new vettedSigningKeysCount
385385
/// value will be set to the nearest range border.
386-
/// @param _nodeOperatorId Node operator id to set reward address for
386+
/// @param _nodeOperatorId Node operator id to set staking limit for
387387
/// @param _vettedSigningKeysCount New staking limit of the node operator
388388
function setNodeOperatorStakingLimit(uint256 _nodeOperatorId, uint64 _vettedSigningKeysCount) external {
389389
_onlyExistedNodeOperator(_nodeOperatorId);
@@ -1439,6 +1439,13 @@ contract NodeOperatorsRegistry is AragonApp, Versioned {
14391439
require(bytes(_name).length > 0 && bytes(_name).length <= MAX_NODE_OPERATOR_NAME_LENGTH, "WRONG_NAME_LENGTH");
14401440
}
14411441

1442+
function _onlyValidRewardAddress(address _rewardAddress) internal view {
1443+
_onlyNonZeroAddress(_rewardAddress);
1444+
// The Lido address is forbidden explicitly because stETH transfers on this contract will revert
1445+
// See onExitedAndStuckValidatorsCountsUpdated() and StETH._transferShares() for details
1446+
require(_rewardAddress != getLocator().lido(), "LIDO_REWARD_ADDRESS");
1447+
}
1448+
14421449
function _onlyNonZeroAddress(address _a) internal pure {
14431450
require(_a != address(0), "ZERO_ADDRESS");
14441451
}

contracts/0.8.9/Burner.sol

+6-7
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ contract Burner is IBurner, AccessControlEnumerable {
6161
error ZeroAddress(string field);
6262

6363
bytes32 public constant REQUEST_BURN_MY_STETH_ROLE = keccak256("REQUEST_BURN_MY_STETH_ROLE");
64-
bytes32 public constant RECOVER_ASSETS_ROLE = keccak256("RECOVER_ASSETS_ROLE");
6564
bytes32 public constant REQUEST_BURN_SHARES_ROLE = keccak256("REQUEST_BURN_SHARES_ROLE");
6665

6766
uint256 private coverSharesBurnRequested;
@@ -164,7 +163,7 @@ contract Burner is IBurner, AccessControlEnumerable {
164163
*
165164
*/
166165
function requestBurnMyStETHForCover(uint256 _stETHAmountToBurn) external onlyRole(REQUEST_BURN_MY_STETH_ROLE) {
167-
require(IStETH(STETH).transferFrom(msg.sender, address(this), _stETHAmountToBurn));
166+
IStETH(STETH).transferFrom(msg.sender, address(this), _stETHAmountToBurn);
168167
uint256 sharesAmount = IStETH(STETH).getSharesByPooledEth(_stETHAmountToBurn);
169168
_requestBurn(sharesAmount, _stETHAmountToBurn, true /* _isCover */);
170169
}
@@ -197,7 +196,7 @@ contract Burner is IBurner, AccessControlEnumerable {
197196
*
198197
*/
199198
function requestBurnMyStETH(uint256 _stETHAmountToBurn) external onlyRole(REQUEST_BURN_MY_STETH_ROLE) {
200-
require(IStETH(STETH).transferFrom(msg.sender, address(this), _stETHAmountToBurn));
199+
IStETH(STETH).transferFrom(msg.sender, address(this), _stETHAmountToBurn);
201200
uint256 sharesAmount = IStETH(STETH).getSharesByPooledEth(_stETHAmountToBurn);
202201
_requestBurn(sharesAmount, _stETHAmountToBurn, false /* _isCover */);
203202
}
@@ -223,15 +222,15 @@ contract Burner is IBurner, AccessControlEnumerable {
223222
* but not marked for burning) to the Lido treasury address set upon the
224223
* contract construction.
225224
*/
226-
function recoverExcessStETH() external onlyRole(RECOVER_ASSETS_ROLE) {
225+
function recoverExcessStETH() external {
227226
uint256 excessStETH = getExcessStETH();
228227

229228
if (excessStETH > 0) {
230229
uint256 excessSharesAmount = IStETH(STETH).getSharesByPooledEth(excessStETH);
231230

232231
emit ExcessStETHRecovered(msg.sender, excessStETH, excessSharesAmount);
233232

234-
require(IStETH(STETH).transfer(TREASURY, excessStETH));
233+
IStETH(STETH).transfer(TREASURY, excessStETH);
235234
}
236235
}
237236

@@ -249,7 +248,7 @@ contract Burner is IBurner, AccessControlEnumerable {
249248
* @param _token an ERC20-compatible token
250249
* @param _amount token amount
251250
*/
252-
function recoverERC20(address _token, uint256 _amount) external onlyRole(RECOVER_ASSETS_ROLE) {
251+
function recoverERC20(address _token, uint256 _amount) external {
253252
if (_amount == 0) revert ZeroRecoveryAmount();
254253
if (_token == STETH) revert StETHRecoveryWrongFunc();
255254

@@ -265,7 +264,7 @@ contract Burner is IBurner, AccessControlEnumerable {
265264
* @param _token an ERC721-compatible token
266265
* @param _tokenId minted token id
267266
*/
268-
function recoverERC721(address _token, uint256 _tokenId) external onlyRole(RECOVER_ASSETS_ROLE) {
267+
function recoverERC721(address _token, uint256 _tokenId) external {
269268
if (_token == STETH) revert StETHRecoveryWrongFunc();
270269

271270
emit ERC721Recovered(msg.sender, _token, _tokenId);

contracts/0.8.9/DepositSecurityModule.sol

+11
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ contract DepositSecurityModule {
7272
IStakingRouter public immutable STAKING_ROUTER;
7373
IDepositContract public immutable DEPOSIT_CONTRACT;
7474

75+
/**
76+
* NB: both `maxDepositsPerBlock` and `minDepositBlockDistance` values
77+
* must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit`
78+
* (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function)
79+
*/
7580
uint256 internal maxDepositsPerBlock;
7681
uint256 internal minDepositBlockDistance;
7782
uint256 internal pauseIntentValidityPeriodBlocks;
@@ -176,6 +181,9 @@ contract DepositSecurityModule {
176181

177182
/**
178183
* Sets `maxDepositsPerBlock`. Only callable by the owner.
184+
*
185+
* NB: the value must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit`
186+
* (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function)
179187
*/
180188
function setMaxDeposits(uint256 newValue) external onlyOwner {
181189
_setMaxDeposits(newValue);
@@ -195,6 +203,9 @@ contract DepositSecurityModule {
195203

196204
/**
197205
* Sets `minDepositBlockDistance`. Only callable by the owner.
206+
*
207+
* NB: the value must be harmonized with `OracleReportSanityChecker.churnValidatorsPerDayLimit`
208+
* (see docs for the `OracleReportSanityChecker.setChurnValidatorsPerDayLimit` function)
198209
*/
199210
function setMinDepositBlockDistance(uint256 newValue) external onlyOwner {
200211
_setMinDepositBlockDistance(newValue);

contracts/0.8.9/OracleDaemonConfig.sol

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ contract OracleDaemonConfig is AccessControlEnumerable {
3838
function update(string calldata _key, bytes calldata _value) external onlyRole(CONFIG_MANAGER_ROLE) {
3939
if (values[_key].length == 0) revert ValueDoesntExist(_key);
4040
if (_value.length == 0) revert EmptyValue(_key);
41+
if (keccak256(values[_key]) == keccak256(_value)) revert ValueIsSame(_key, _value);
4142
values[_key] = _value;
4243

4344
emit ConfigValueUpdated(_key, _value);
@@ -76,6 +77,7 @@ contract OracleDaemonConfig is AccessControlEnumerable {
7677
error EmptyValue(string key);
7778
error ValueDoesntExist(string key);
7879
error ZeroAddress();
80+
error ValueIsSame(string key, bytes value);
7981

8082
event ConfigValueSet(string key, bytes value);
8183
event ConfigValueUpdated(string key, bytes value);

contracts/0.8.9/StakingRouter.sol

+3-3
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version
497497
uint256 totalExitedValidators,
498498
/* uint256 totalDepositedValidators */,
499499
/* uint256 depositableValidatorsCount */
500-
) = IStakingModule(stakingModule.stakingModuleAddress).getNodeOperatorSummary(_nodeOperatorId);
500+
) = IStakingModule(moduleAddr).getNodeOperatorSummary(_nodeOperatorId);
501501

502502
if (_correction.currentModuleExitedValidatorsCount != stakingModule.exitedValidatorsCount ||
503503
_correction.currentNodeOperatorExitedValidatorsCount != totalExitedValidators ||
@@ -1038,8 +1038,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version
10381038
}
10391039
}
10401040

1041-
// sanity check
1042-
if (totalFee >= precisionPoints) revert ValueOver100Percent("totalFee");
1041+
// Total fee never exceeds 100%
1042+
assert(totalFee <= precisionPoints);
10431043

10441044
/// @dev shrink arrays
10451045
if (rewardedStakingModulesCount < stakingModulesCount) {

0 commit comments

Comments
 (0)