diff --git a/contracts/contract/minipool/RocketMinipoolDelegate.sol b/contracts/contract/minipool/RocketMinipoolDelegate.sol index 82341f5ce..6efac87bd 100644 --- a/contracts/contract/minipool/RocketMinipoolDelegate.sol +++ b/contracts/contract/minipool/RocketMinipoolDelegate.sol @@ -33,6 +33,9 @@ contract RocketMinipoolDelegate is RocketMinipoolStorageLayout, RocketMinipoolIn uint256 constant prelaunchAmount = 16 ether; // The amount of ETH initially deposited when minipool is created uint256 constant distributionCooldown = 100; // Number of blocks that must pass between calls to distributeBalance + bytes32 private constant queueKeyFull = keccak256("minipools.available.full"); + bytes32 private constant queueKeyHalf = keccak256("minipools.available.half"); + // Libs using SafeMath for uint; @@ -143,6 +146,23 @@ contract RocketMinipoolDelegate is RocketMinipoolStorageLayout, RocketMinipoolIn preStake(_validatorPubkey, _validatorSignature, _depositDataRoot); } + // Upgrade an existing half deposit pool waiting for user deposit (match to start minipool) to a full deposit pool waiting for user deposit (refund) + function nodeDepositHalfToFull() override external onlyMinipoolOwnerOrWithdrawalAddress(msg.sender) onlyInitialised { + require(depositType == MinipoolDeposit.Half, "Must be a current Half deposit pool"); + require(status == MinipoolStatus.Initialised, "Must be waiting for user match"); + require(msg.value + nodeDepositBalance == rocketDAOProtocolSettingsMinipool.getFullDepositNodeAmount(), "Deposit amount must get DepositBalance to FullDeposit amount"); + + // Update to Full deposit + nodeDepositBalance.add(msg.value); + depositType = MinipoolDeposit.Full; + setStatus(MinipoolStatus.Prelaunch); + + // remove from half queue and put into full queue + RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")); + rocketMinipoolQueue.removeMinipool(queueKeyHalf); + rocketMinipoolQueue.enqueueMinipool(queueKeyFull); + } + // Assign user deposited ETH to the minipool and mark it as prelaunch // Only accepts calls from the RocketDepositPool contract function userDeposit() override external payable onlyLatestContract("rocketDepositPool", msg.sender) onlyInitialised { @@ -172,6 +192,34 @@ contract RocketMinipoolDelegate is RocketMinipoolStorageLayout, RocketMinipoolIn _refund(); } + // Refund node by minting rETH instead of waiting for user deposited ETH + function reth_refund() override external onlyMinipoolOwnerOrWithdrawalAddress(msg.sender) onlyInitialised { + // Check refund balance + require(nodeRefundBalance == 0, "reth_refund should only be used when no user ETH has been assigned for the refund; please call refund()"); + require(nodeDepositBalance == rocketDAOProtocolSettingsMinipool.getFullDepositNodeAmount(), + "reth_refund should only be used when a Full deposit was made and no refund has occurred"); + + // rETH minting will come from a virtual "User deposit" and reflect in balances; the user here just happens to be the NO + virtualDepositValue = rocketDAOProtocolSettingsMinipool.getHalfDepositNodeAmount(); + nodeDepositBalance = nodeDepositBalance.sub(virtualDepositValue); + userDepositBalance = userDepositBalance.add(virtualDepositValue); + emit DepositReceived(msg.sender, virtualDepositValue, block.timestamp); + + // Mint rETH to withdrawal address using the "User deposit" + address nodeWithdrawalAddress = rocketStorage.getNodeWithdrawalAddress(nodeAddress); + RocketTokenRETHInterface rocketTokenRETH = RocketTokenRETHInterface(getContractAddress("rocketTokenRETH")); + rocketTokenRETH.mint(virtualDepositValue, nodeWithdrawalAddress); + emit EtherWithdrawn(nodeWithdrawalAddress, virtualDepositValue, block.timestamp); + + // Remove from queue + // Note: the remove actually swaps the last item in the queue to the location occupied by + // this minipool and then drops this minipool. This is computationally cheap, but could be + // seen as unfair. That said - it's probably close enough. Someone moves up in the queue, + // but nobody is actually pushed back any slots. + RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")); + rocketMinipoolQueue.removeMinipool(queueKeyFull); + } + // Called to slash node operator's RPL balance if withdrawal balance was less than user deposit function slash() external override onlyInitialised { // Check there is a slash balance diff --git a/contracts/contract/minipool/RocketMinipoolQueue.sol b/contracts/contract/minipool/RocketMinipoolQueue.sol index 9afcd055b..7ea7a6425 100644 --- a/contracts/contract/minipool/RocketMinipoolQueue.sol +++ b/contracts/contract/minipool/RocketMinipoolQueue.sol @@ -101,6 +101,16 @@ contract RocketMinipoolQueue is RocketBase, RocketMinipoolQueueInterface { return (MinipoolDeposit.None, 0); } + // Add a minipool to the end of the appropriate queue + // Only accepts calls from registered minipools + function enqueueMinipool(MinipoolDeposit _depositType) override external onlyLatestContract("rocketMinipoolQueue", address(this)) onlyRegisteredMinipool(msg.sender) { + // Remove minipool from queue + if (_depositType == MinipoolDeposit.Half) { return enqueueMinipool(queueKeyHalf, msg.sender); } + if (_depositType == MinipoolDeposit.Full) { return enqueueMinipool(queueKeyFull, msg.sender); } + if (_depositType == MinipoolDeposit.Empty) { return enqueueMinipool(queueKeyEmpty, msg.sender); } + require(false, "Invalid minipool deposit type"); + } + // Add a minipool to the end of the appropriate queue // Only accepts calls from the RocketMinipoolManager contract function enqueueMinipool(MinipoolDeposit _depositType, address _minipool) override external onlyLatestContract("rocketMinipoolQueue", address(this)) onlyLatestContract("rocketMinipoolManager", msg.sender) { diff --git a/contracts/interface/minipool/RocketMinipoolQueueInterface.sol b/contracts/interface/minipool/RocketMinipoolQueueInterface.sol index bbe9c272b..f7336ea61 100644 --- a/contracts/interface/minipool/RocketMinipoolQueueInterface.sol +++ b/contracts/interface/minipool/RocketMinipoolQueueInterface.sol @@ -12,6 +12,7 @@ interface RocketMinipoolQueueInterface { function getNextCapacity() external view returns (uint256); function getNextDeposit() external view returns (MinipoolDeposit, uint256); function enqueueMinipool(MinipoolDeposit _depositType, address _minipool) external; + function enqueueMinipool(MinipoolDeposit _depositType) external; function dequeueMinipool() external returns (address minipoolAddress); function dequeueMinipoolByDeposit(MinipoolDeposit _depositType) external returns (address minipoolAddress); function removeMinipool(MinipoolDeposit _depositType) external;