22
33pragma solidity ^ 0.8.27 ;
44
5+ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol " ;
56import {IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol " ;
6- import {ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol " ;
7- import {ERC1363 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol " ;
7+ import {ERC1363Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC1363Upgradeable.sol " ;
88import {ERC4626 , IERC4626 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol " ;
99import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
1010import {Math} from "@openzeppelin/contracts/utils/math/Math.sol " ;
@@ -27,17 +27,24 @@ import {ProtocolStaking} from "./ProtocolStaking.sol";
2727 * may decrease due to slashing. These losses are symmetrically passed to delegators on the `OperatorStaking` level.
2828 * Slashing must first decrease the `ProtocolStaking` balance of this contract before affecting pending withdrawals.
2929 */
30- contract OperatorStaking is ERC1363 , ReentrancyGuardTransient {
30+ contract OperatorStaking is ERC1363Upgradeable , ReentrancyGuardTransient , UUPSUpgradeable {
3131 using Math for uint256 ;
3232 using Checkpoints for Checkpoints.Trace208;
3333
34- ProtocolStaking private immutable _protocolStaking;
35- IERC20 private immutable _asset;
36- address private _rewarder;
37- uint256 private _totalSharesInRedemption;
38- mapping (address => uint256 ) private _sharesReleased;
39- mapping (address => Checkpoints.Trace208) private _redeemRequests;
40- mapping (address => mapping (address => bool )) private _operator;
34+ /// @custom:storage-location erc7201:fhevm_protocol.storage.OperatorStaking
35+ struct OperatorStakingStorage {
36+ ProtocolStaking _protocolStaking;
37+ IERC20 _asset;
38+ address _rewarder;
39+ uint256 _totalSharesInRedemption;
40+ mapping (address => uint256 ) _sharesReleased;
41+ mapping (address => Checkpoints.Trace208) _redeemRequests;
42+ mapping (address => mapping (address => bool )) _operator;
43+ }
44+
45+ // keccak256(abi.encode(uint256(keccak256("fhevm_protocol.storage.OperatorStaking")) - 1)) & ~bytes32(uint256(0xff))
46+ bytes32 private constant OPERATOR_STAKING_STORAGE_LOCATION =
47+ 0x7fc851282090a0d8832502c48739eac98a0856539351f17cb5d5950c860fd200 ;
4148
4249 /// @dev Emitted when an operator is set or unset for a controller.
4350 event OperatorSet (address indexed controller , address indexed operator , bool approved );
@@ -72,6 +79,11 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
7279 _;
7380 }
7481
82+ /// @custom:oz-upgrades-unsafe-allow constructor
83+ constructor () {
84+ _disableInitializers ();
85+ }
86+
7587 /**
7688 * @notice Initializes the OperatorStaking contract.
7789 * @param name The name of the ERC20 token.
@@ -81,24 +93,34 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
8193 * @param initialMaxFeeBasisPoints_ The initial maximum fee basis points for the OperatorRewarder contract.
8294 * @param initialFeeBasisPoints_ The initial fee basis points for the OperatorRewarder contract.
8395 */
84- constructor (
96+ function initialize (
8597 string memory name ,
8698 string memory symbol ,
8799 ProtocolStaking protocolStaking_ ,
88100 address beneficiary_ ,
89101 uint16 initialMaxFeeBasisPoints_ ,
90102 uint16 initialFeeBasisPoints_
91- ) ERC20 (name, symbol) {
92- _asset = IERC20 (protocolStaking_.stakingToken ());
93- _protocolStaking = protocolStaking_;
103+ ) public initializer {
104+ __ERC20_init (name, symbol);
105+
106+ OperatorStakingStorage storage $ = _getOperatorStakingStorage ();
107+
108+ $._asset = IERC20 (protocolStaking_.stakingToken ());
109+ $._protocolStaking = protocolStaking_;
94110
95111 IERC20 (asset ()).approve (address (protocolStaking_), type (uint256 ).max);
96112
97113 address rewarder_ = address (
98- new OperatorRewarder (beneficiary_, protocolStaking_, this , initialMaxFeeBasisPoints_, initialFeeBasisPoints_)
114+ new OperatorRewarder (
115+ beneficiary_,
116+ protocolStaking_,
117+ this ,
118+ initialMaxFeeBasisPoints_,
119+ initialFeeBasisPoints_
120+ )
99121 );
100122 protocolStaking_.setRewardsRecipient (rewarder_);
101- _rewarder = rewarder_;
123+ $. _rewarder = rewarder_;
102124
103125 emit RewarderSet (address (0 ), rewarder_);
104126 }
@@ -133,19 +155,21 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
133155 }
134156 _burn (ownerRedeem, shares);
135157
158+ OperatorStakingStorage storage $ = _getOperatorStakingStorage ();
159+
136160 uint256 newTotalSharesInRedemption = totalSharesInRedemption () + shares;
137- _totalSharesInRedemption = newTotalSharesInRedemption;
161+ $. _totalSharesInRedemption = newTotalSharesInRedemption;
138162
139163 ProtocolStaking protocolStaking_ = protocolStaking ();
140164 int256 assetsToWithdraw = SafeCast.toInt256 (previewRedeem (newTotalSharesInRedemption)) -
141165 SafeCast.toInt256 (
142166 IERC20 (asset ()).balanceOf (address (this )) + protocolStaking_.awaitingRelease (address (this ))
143167 );
144168
145- (, uint48 lastReleaseTime , uint208 controllerSharesRedeemed ) = _redeemRequests[controller].latestCheckpoint ();
169+ (, uint48 lastReleaseTime , uint208 controllerSharesRedeemed ) = $. _redeemRequests[controller].latestCheckpoint ();
146170 uint48 releaseTime = protocolStaking_.unstake (SafeCast.toUint256 (SignedMath.max (assetsToWithdraw, 0 )));
147171 assert (releaseTime >= lastReleaseTime); // should never happen
148- _redeemRequests[controller].push (releaseTime, controllerSharesRedeemed + shares);
172+ $. _redeemRequests[controller].push (releaseTime, controllerSharesRedeemed + shares);
149173
150174 emit RedeemRequest (controller, ownerRedeem, 0 , msg .sender , shares, releaseTime);
151175 }
@@ -174,8 +198,9 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
174198 uint256 assets = previewRedeem (shares);
175199
176200 if (assets > 0 ) {
177- _totalSharesInRedemption -= shares;
178- _sharesReleased[controller] += shares;
201+ OperatorStakingStorage storage $ = _getOperatorStakingStorage ();
202+ $._totalSharesInRedemption -= shares;
203+ $._sharesReleased[controller] += shares;
179204 _doTransferOut (receiver, assets);
180205
181206 emit IERC4626 .Withdraw (msg .sender , receiver, controller, assets, shares);
@@ -200,15 +225,15 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
200225 }
201226
202227 /**
203- * @dev Set a new rewarder contract.
228+ * @dev Set a new rewarder contract. Only callable by the owner.
204229 * @param newRewarder The new rewarder contract address. This contract must not be the same as the current
205230 * and must have code.
206231 */
207232 function setRewarder (address newRewarder ) public virtual onlyOwner {
208233 address oldRewarder = rewarder ();
209234 require (newRewarder != oldRewarder && newRewarder.code.length > 0 , InvalidRewarder (newRewarder));
210235 OperatorRewarder (oldRewarder).shutdown ();
211- _rewarder = newRewarder;
236+ _getOperatorStakingStorage (). _rewarder = newRewarder;
212237 protocolStaking ().setRewardsRecipient (newRewarder);
213238
214239 emit RewarderSet (oldRewarder, newRewarder);
@@ -220,7 +245,7 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
220245 * @param approved True to approve, false to revoke.
221246 */
222247 function setOperator (address operator , bool approved ) public virtual {
223- _operator[msg .sender ][operator] = approved;
248+ _getOperatorStakingStorage (). _operator[msg .sender ][operator] = approved;
224249
225250 emit OperatorSet (msg .sender , operator, approved);
226251 }
@@ -238,23 +263,23 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
238263 * @return The asset address.
239264 */
240265 function asset () public view virtual returns (address ) {
241- return address (_asset);
266+ return address (_getOperatorStakingStorage (). _asset);
242267 }
243268
244269 /**
245270 * @notice Returns the ProtocolStaking contract address.
246271 * @return The ProtocolStaking contract address.
247272 */
248273 function protocolStaking () public view virtual returns (ProtocolStaking) {
249- return _protocolStaking;
274+ return _getOperatorStakingStorage (). _protocolStaking;
250275 }
251276
252277 /**
253278 * @notice Returns the rewarder contract address.
254279 * @return The rewarder contract address.
255280 */
256281 function rewarder () public view virtual returns (address ) {
257- return _rewarder;
282+ return _getOperatorStakingStorage (). _rewarder;
258283 }
259284
260285 /**
@@ -275,7 +300,8 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
275300 * @return Amount of shares pending redeem.
276301 */
277302 function pendingRedeemRequest (uint256 , address controller ) public view virtual returns (uint256 ) {
278- return _redeemRequests[controller].latest () - _redeemRequests[controller].upperLookup (Time.timestamp ());
303+ OperatorStakingStorage storage $ = _getOperatorStakingStorage ();
304+ return $._redeemRequests[controller].latest () - $._redeemRequests[controller].upperLookup (Time.timestamp ());
279305 }
280306
281307 /**
@@ -284,15 +310,16 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
284310 * @return Amount of claimable shares.
285311 */
286312 function claimableRedeemRequest (uint256 , address controller ) public view virtual returns (uint256 ) {
287- return _redeemRequests[controller].upperLookup (Time.timestamp ()) - _sharesReleased[controller];
313+ OperatorStakingStorage storage $ = _getOperatorStakingStorage ();
314+ return $._redeemRequests[controller].upperLookup (Time.timestamp ()) - $._sharesReleased[controller];
288315 }
289316
290317 /**
291318 * @notice Returns the total shares in redemption.
292319 * @return The total shares in redemption.
293320 */
294321 function totalSharesInRedemption () public view virtual returns (uint256 ) {
295- return _totalSharesInRedemption;
322+ return _getOperatorStakingStorage (). _totalSharesInRedemption;
296323 }
297324
298325 /**
@@ -337,7 +364,7 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
337364 * @return True if operator is approved, false otherwise.
338365 */
339366 function isOperator (address controller , address operator ) public view virtual returns (bool ) {
340- return _operator[controller][operator];
367+ return _getOperatorStakingStorage (). _operator[controller][operator];
341368 }
342369
343370 function _doTransferOut (address to , uint256 amount ) internal {
@@ -371,6 +398,8 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
371398 emit IERC4626 .Deposit (caller, receiver, assets, shares);
372399 }
373400
401+ function _authorizeUpgrade (address newImplementation ) internal override onlyOwner {}
402+
374403 function _convertToShares (uint256 assets , Math.Rounding rounding ) internal view virtual returns (uint256 ) {
375404 // Shares in redemption have not yet received assets, so we need to account for them in the conversion.
376405 return
@@ -394,4 +423,10 @@ contract OperatorStaking is ERC1363, ReentrancyGuardTransient {
394423 function _decimalsOffset () internal view virtual returns (uint8 ) {
395424 return 0 ;
396425 }
426+
427+ function _getOperatorStakingStorage () private pure returns (OperatorStakingStorage storage $) {
428+ assembly {
429+ $.slot := OPERATOR_STAKING_STORAGE_LOCATION
430+ }
431+ }
397432}
0 commit comments