@@ -8,6 +8,7 @@ import {IERC20} from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
8
8
import {UnstructuredStorage} from "@aragon/os/contracts/common/UnstructuredStorage.sol " ;
9
9
import {SafeMath} from "@aragon/os/contracts/lib/math/SafeMath.sol " ;
10
10
import {Pausable} from "./utils/Pausable.sol " ;
11
+ import {UnstructuredStorageUint128} from "./utils/UnstructuredStorageUint128.sol " ;
11
12
12
13
/**
13
14
* @title Interest-bearing ERC20-like token for Lido Liquid Stacking protocol.
@@ -49,9 +50,11 @@ import {Pausable} from "./utils/Pausable.sol";
49
50
contract StETH is IERC20 , Pausable {
50
51
using SafeMath for uint256 ;
51
52
using UnstructuredStorage for bytes32 ;
53
+ using UnstructuredStorageUint128 for bytes32 ;
52
54
53
55
address constant internal INITIAL_TOKEN_HOLDER = 0xdead ;
54
56
uint256 constant internal INFINITE_ALLOWANCE = ~ uint256 (0 );
57
+ uint256 constant internal UINT128_MAX = ~ uint128 (0 );
55
58
56
59
/**
57
60
* @dev StETH balances are dynamic and are calculated based on the accounts' shares
@@ -82,6 +85,8 @@ contract StETH is IERC20, Pausable {
82
85
* see https://github.com/lidofinance/lido-dao/issues/181#issuecomment-736098834
83
86
*
84
87
* keccak256("lido.StETH.totalShares")
88
+ *
89
+ * @dev Since version 3, high 128 bits can be used to store the external shares from Lido contract
85
90
*/
86
91
bytes32 internal constant TOTAL_SHARES_POSITION =
87
92
0xe3b4b636e601189b5f4c6742edf2538ac12bb61ed03e6da26949d69838fa447e ;
@@ -299,37 +304,45 @@ contract StETH is IERC20, Pausable {
299
304
}
300
305
301
306
/**
307
+ * @param _ethAmount the amount of ether to convert to shares. Must be less than UINT128_MAX.
302
308
* @return the amount of shares that corresponds to `_ethAmount` protocol-controlled Ether.
309
+ * @dev the result is rounded down.
303
310
*/
304
311
function getSharesByPooledEth (uint256 _ethAmount ) public view returns (uint256 ) {
305
- return _ethAmount
306
- .mul (_getShareRateDenominator ()) // denominator in shares
307
- .div (_getShareRateNumerator ()); // numerator in ether
312
+ require (_ethAmount < UINT128_MAX, "ETH_TOO_LARGE " );
313
+ return (_ethAmount
314
+ * _getShareRateDenominator ()) // denominator in shares
315
+ / _getShareRateNumerator (); // numerator in ether
308
316
}
309
317
310
318
/**
319
+ * @param _sharesAmount the amount of shares to convert to ether. Must be less than UINT128_MAX.
311
320
* @return the amount of ether that corresponds to `_sharesAmount` token shares.
321
+ * @dev the result is rounded down.
312
322
*/
313
323
function getPooledEthByShares (uint256 _sharesAmount ) public view returns (uint256 ) {
314
- return _sharesAmount
315
- .mul (_getShareRateNumerator ()) // numerator in ether
316
- .div (_getShareRateDenominator ()); // denominator in shares
324
+ require (_sharesAmount < UINT128_MAX, "SHARES_TOO_LARGE " );
325
+ return (_sharesAmount
326
+ * _getShareRateNumerator ()) // numerator in ether
327
+ / _getShareRateDenominator (); // denominator in shares
317
328
}
318
329
319
330
/**
331
+ * @param _sharesAmount the amount of shares to convert to ether. Must be less than UINT128_MAX.
320
332
* @return the amount of ether that corresponds to `_sharesAmount` token shares.
321
333
* @dev The result is rounded up. So,
322
334
* for `shareRate >= 0.5`, `getSharesByPooledEth(getPooledEthBySharesRoundUp(1))` will be 1.
323
335
*/
324
336
function getPooledEthBySharesRoundUp (uint256 _sharesAmount ) public view returns (uint256 etherAmount ) {
337
+ require (_sharesAmount < UINT128_MAX, "SHARES_TOO_LARGE " );
325
338
uint256 numeratorInEther = _getShareRateNumerator ();
326
339
uint256 denominatorInShares = _getShareRateDenominator ();
327
340
328
- etherAmount = _sharesAmount
329
- . mul ( numeratorInEther)
330
- . div ( denominatorInShares) ;
341
+ etherAmount = ( _sharesAmount
342
+ * numeratorInEther)
343
+ / denominatorInShares;
331
344
332
- if (_sharesAmount. mul ( numeratorInEther) != etherAmount. mul ( denominatorInShares) ) {
345
+ if (_sharesAmount * numeratorInEther != etherAmount * denominatorInShares) {
333
346
++ etherAmount;
334
347
}
335
348
}
@@ -392,6 +405,7 @@ contract StETH is IERC20, Pausable {
392
405
/**
393
406
* @return the numerator of the protocol's share rate (in ether).
394
407
* @dev used to convert shares to tokens and vice versa.
408
+ * @dev can be overridden in a derived contract.
395
409
*/
396
410
function _getShareRateNumerator () internal view returns (uint256 ) {
397
411
return _getTotalPooledEther ();
@@ -400,6 +414,7 @@ contract StETH is IERC20, Pausable {
400
414
/**
401
415
* @return the denominator of the protocol's share rate (in shares).
402
416
* @dev used to convert shares to tokens and vice versa.
417
+ * @dev can be overridden in a derived contract.
403
418
*/
404
419
function _getShareRateDenominator () internal view returns (uint256 ) {
405
420
return _getTotalShares ();
@@ -456,7 +471,7 @@ contract StETH is IERC20, Pausable {
456
471
* @return the total amount of shares in existence.
457
472
*/
458
473
function _getTotalShares () internal view returns (uint256 ) {
459
- return TOTAL_SHARES_POSITION.getStorageUint256 ();
474
+ return TOTAL_SHARES_POSITION.getLowUint128 ();
460
475
}
461
476
462
477
/**
@@ -504,7 +519,7 @@ contract StETH is IERC20, Pausable {
504
519
require (_recipient != address (0 ), "MINT_TO_ZERO_ADDR " );
505
520
506
521
newTotalShares = _getTotalShares ().add (_sharesAmount);
507
- TOTAL_SHARES_POSITION.setStorageUint256 ( newTotalShares);
522
+ TOTAL_SHARES_POSITION.setLowUint128 ( uint128 ( newTotalShares) );
508
523
509
524
shares[_recipient] = shares[_recipient].add (_sharesAmount);
510
525
@@ -535,7 +550,7 @@ contract StETH is IERC20, Pausable {
535
550
uint256 preRebaseTokenAmount = getPooledEthByShares (_sharesAmount);
536
551
537
552
newTotalShares = _getTotalShares ().sub (_sharesAmount);
538
- TOTAL_SHARES_POSITION.setStorageUint256 ( newTotalShares);
553
+ TOTAL_SHARES_POSITION.setLowUint128 ( uint128 ( newTotalShares) );
539
554
540
555
shares[_account] = accountShares.sub (_sharesAmount);
541
556
0 commit comments