11// SPDX-License-Identifier: GPL-3.0-only
22pragma solidity 0.8.30 ;
33
4- import "../../interface/RocketVaultInterface.sol " ;
5-
6- import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol " ;
7- import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol " ;
8- import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsRewardsInterface.sol " ;
9- import "../../interface/minipool/RocketMinipoolManagerInterface.sol " ;
10- import "../../interface/network/RocketNetworkPricesInterface.sol " ;
11- import "../../interface/network/RocketNetworkSnapshotsInterface.sol " ;
12- import "../../interface/network/RocketNetworkVotingInterface.sol " ;
13- import "../../interface/node/RocketNodeManagerInterface.sol " ;
14- import "../../interface/node/RocketNodeStakingInterface.sol " ;
15- import "../../interface/token/RocketTokenRPLInterface.sol " ;
16- import "../../interface/util/AddressSetStorageInterface.sol " ;
17- import "../../interface/util/IERC20.sol " ;
18- import "../RocketBase.sol " ;
19- import "../network/RocketNetworkSnapshots.sol " ;
4+ import {RocketStorageInterface} from "../../interface/RocketStorageInterface.sol " ;
5+ import {RocketVaultInterface} from "../../interface/RocketVaultInterface.sol " ;
6+ import {RocketDAOProtocolSettingsNodeInterface} from "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol " ;
7+ import {RocketMinipoolManagerInterface} from "../../interface/minipool/RocketMinipoolManagerInterface.sol " ;
8+ import {RocketNetworkPricesInterface} from "../../interface/network/RocketNetworkPricesInterface.sol " ;
9+ import {RocketNetworkSnapshotsInterface} from "../../interface/network/RocketNetworkSnapshotsInterface.sol " ;
10+ import {RocketNodeManagerInterface} from "../../interface/node/RocketNodeManagerInterface.sol " ;
11+ import {RocketNodeStakingInterface} from "../../interface/node/RocketNodeStakingInterface.sol " ;
12+ import {RocketTokenRPLInterface} from "../../interface/token/RocketTokenRPLInterface.sol " ;
13+ import {IERC20 } from "../../interface/util/IERC20.sol " ;
14+ import {IERC20Burnable } from "../../interface/util/IERC20Burnable.sol " ;
15+ import {RocketBase} from "../RocketBase.sol " ;
2016
2117/// @notice Handles staking of RPL by node operators
2218contract RocketNodeStaking is RocketBase , RocketNodeStakingInterface {
@@ -192,8 +188,12 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
192188
193189 /// @dev Internal implementation for staking process
194190 function _stakeRPLFor (address _nodeAddress , uint256 _amount ) internal {
191+ // Transfer RPL in and increase stake
195192 transferRPLIn (msg .sender , _amount);
196193 increaseNodeRPLStake (_nodeAddress, _amount);
194+ // Update last staked time
195+ setNodeLastStakeTime (_nodeAddress);
196+ // Emit event
197197 emit RPLStaked (_nodeAddress, msg .sender , _amount, block .timestamp );
198198 }
199199
@@ -254,6 +254,10 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
254254 if (timeSinceLastUnstake <= unstakingPeriod) {
255255 return 0 ;
256256 }
257+ // Check withdrawal cooldown
258+ if (block .timestamp - getNodeRPLStakedTime (_nodeAddress) < rocketDAOProtocolSettingsNode.getWithdrawalCooldown ()) {
259+ return 0 ;
260+ }
257261 // Retrieve amount of RPL in unstaking state
258262 bytes32 unstakingKey = keccak256 (abi.encodePacked ("rpl.megapool.unstaking.amount " , _nodeAddress));
259263 uint256 amountToWithdraw = getUint (unstakingKey);
@@ -366,9 +370,9 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
366370 uint256 rplSlashAmount = calcBase * _ethSlashAmount / rocketNetworkPrices.getRPLPrice ();
367371 // Cap slashed amount to node's RPL stake
368372 uint256 rplStake = getNodeLegacyStakedRPL (_nodeAddress);
369- if (rplSlashAmount > rplStake) { rplSlashAmount = rplStake; }
373+ if (rplSlashAmount > rplStake) {rplSlashAmount = rplStake;}
370374 // Transfer slashed amount to auction contract
371- if (rplSlashAmount > 0 ) rocketVault.transferToken ("rocketAuctionManager " , IERC20 (getContractAddress ("rocketTokenRPL " )), rplSlashAmount);
375+ if (rplSlashAmount > 0 ) rocketVault.transferToken ("rocketAuctionManager " , IERC20 (getContractAddress ("rocketTokenRPL " )), rplSlashAmount);
372376 // Update RPL stake amounts
373377 decreaseNodeLegacyRPLStake (_nodeAddress, rplSlashAmount);
374378 // Mark minipool as slashed
@@ -426,7 +430,7 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
426430 // Check node operator has sufficient RPL to reduce
427431 uint256 legacyStakedRPL = getNodeLegacyStakedRPL (_nodeAddress);
428432 uint256 lockedRPL = getNodeLockedRPL (_nodeAddress);
429- require (
433+ require (
430434 uint256 (totalStakedRPL) >= _amount + lockedRPL &&
431435 uint256 (totalStakedRPL) >= _amount + legacyStakedRPL,
432436 "Insufficient RPL stake to reduce "
@@ -450,13 +454,13 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
450454 uint256 legacyStakedRPL = getUint (legacyKey);
451455 // Check amount after decrease does not fall below minimum requirement for minipool bond
452456 uint256 maximumStakedRPL = getNodeMaximumRPLStakeForMinipools (_nodeAddress);
453- require (
457+ require (
454458 legacyStakedRPL >= _amount + maximumStakedRPL,
455459 "Insufficient legacy staked RPL "
456460 );
457461 uint256 lockedRPL = getNodeLockedRPL (_nodeAddress);
458462 // Check node has enough unlocked RPL for the reduction
459- require (
463+ require (
460464 uint256 (totalStakedRPL) >= _amount + lockedRPL,
461465 "Insufficient RPL stake to reduce "
462466 );
@@ -546,7 +550,7 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
546550 /// @dev If legacy RPL balance has not been migrated, migrate it. Otherwise, do nothing
547551 function migrateLegacy (address _nodeAddress , uint256 _amount ) private {
548552 bytes32 migratedKey = keccak256 (abi.encodePacked ("rpl.legacy.staked.node.migrated " , _nodeAddress));
549- if (getBool (migratedKey) ) {
553+ if (getBool (migratedKey)) {
550554 return ;
551555 }
552556 bytes32 legacyKey = keccak256 (abi.encodePacked ("rpl.legacy.staked.node.amount " , _nodeAddress));
@@ -575,6 +579,11 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
575579 setUint (keccak256 (abi.encodePacked ("rpl.megapool.unstake.time " , _nodeAddress)), block .timestamp );
576580 }
577581
582+ /// @dev Sets the time of the given node operator's stake to the current block time
583+ function setNodeLastStakeTime (address _nodeAddress ) internal {
584+ setUint (keccak256 (abi.encodePacked ("rpl.staked.node.time " , _nodeAddress)), block .timestamp );
585+ }
586+
578587 /// @dev Implements caller restrictions (per RPIP-31):
579588 /// - If a node’s RPL withdrawal address is unset, the call MUST come from one of: the node’s primary withdrawal address, or the node’s address
580589 /// - If a node’s RPL withdrawal address is set, the call MUST come from the current RPL withdrawal address
0 commit comments