From 1f50b4539eccb7b94a83ac121f2bf046f2ee8ead Mon Sep 17 00:00:00 2001 From: Fornax <23104993+fornax2@users.noreply.github.com> Date: Tue, 10 Sep 2024 00:52:43 -0300 Subject: [PATCH] Megapool - WIP --- contracts/contract/RocketBase.sol | 8 + .../RocketDAOProtocolSettingsMegapool.sol | 26 ++ .../contract/deposit/RocketDepositPool.sol | 87 +++-- .../contract/helper/RocketNodeDepositLEB4.sol | 360 ------------------ .../contract/megapool/RocketMegapool.sol | 268 +++++++++++++ .../megapool/RocketMegapoolFactory.sol | 43 +++ contracts/contract/node/RocketNodeDeposit.sol | 141 +++---- contracts/contract/node/RocketNodeManager.sol | 29 ++ ...etDAOProtocolSettingsMegapoolInterface.sol | 8 + .../deposit/RocketDepositPoolInterface.sol | 2 + .../RocketMegapoolFactoryInterface.sol | 9 + .../megapool/RocketMegapoolInterface.sol | 17 + .../node/RocketNodeDepositInterface.sol | 5 +- .../node/RocketNodeManagerInterface.sol | 2 + 14 files changed, 520 insertions(+), 485 deletions(-) create mode 100644 contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsMegapool.sol delete mode 100644 contracts/contract/helper/RocketNodeDepositLEB4.sol create mode 100644 contracts/contract/megapool/RocketMegapool.sol create mode 100644 contracts/contract/megapool/RocketMegapoolFactory.sol create mode 100644 contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsMegapoolInterface.sol create mode 100644 contracts/interface/megapool/RocketMegapoolFactoryInterface.sol create mode 100644 contracts/interface/megapool/RocketMegapoolInterface.sol diff --git a/contracts/contract/RocketBase.sol b/contracts/contract/RocketBase.sol index d5ace4f29..c260467bf 100644 --- a/contracts/contract/RocketBase.sol +++ b/contracts/contract/RocketBase.sol @@ -60,6 +60,14 @@ abstract contract RocketBase { require(getBool(keccak256(abi.encodePacked("minipool.exists", _minipoolAddress))), "Invalid minipool"); _; } + + /** + * @dev Throws if called by any sender that isn't a registered megapool + */ + modifier onlyRegisteredMegapool(address _megapoolAddress) { + require(getBool(keccak256(abi.encodePacked("megapool.exists", _megapoolAddress))), "Invalid megapool"); + _; + } /** diff --git a/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsMegapool.sol b/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsMegapool.sol new file mode 100644 index 000000000..a47c17d20 --- /dev/null +++ b/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsMegapool.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity 0.8.18; + +import "./RocketDAOProtocolSettings.sol"; +import "../../../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMegapoolInterface.sol"; + +/// @notice Network minipool settings +contract RocketDAOProtocolSettingsMegapool is RocketDAOProtocolSettings, RocketDAOProtocolSettingsMegapoolInterface { + + + constructor(RocketStorageInterface _rocketStorageAddress) RocketDAOProtocolSettings(_rocketStorageAddress, "minipool") { + version = 1; + // Initialize settings on deployment + if(!getBool(keccak256(abi.encodePacked(settingNameSpace, "deployed")))) { + // Apply settings + // Settings initialised + setBool(keccak256(abi.encodePacked(settingNameSpace, "deployed")), true); + } + } + + /// @notice + function getUnstakingPeriod() override external view returns (uint256) { + return getSettingUint("megapool.unstaking.period"); + } + +} diff --git a/contracts/contract/deposit/RocketDepositPool.sol b/contracts/contract/deposit/RocketDepositPool.sol index 48f205ee3..46577e596 100644 --- a/contracts/contract/deposit/RocketDepositPool.sol +++ b/contracts/contract/deposit/RocketDepositPool.sol @@ -1,9 +1,5 @@ // SPDX-License-Identifier: GPL-3.0-only -pragma solidity 0.7.6; - -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@openzeppelin/contracts/math/SignedSafeMath.sol"; -import "@openzeppelin/contracts/utils/SafeCast.sol"; +pragma solidity 0.8.18; import "../RocketBase.sol"; import "../../interface/RocketVaultInterface.sol"; @@ -15,16 +11,14 @@ import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsDepositIn import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol"; import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNetworkInterface.sol"; import "../../interface/token/RocketTokenRETHInterface.sol"; +import "../../interface/util/LinkedListStorageInterface.sol"; +import "../../interface/megapool/RocketMegapoolInterface.sol"; +import "../../interface/node/RocketNodeManagerInterface.sol"; import "../../types/MinipoolDeposit.sol"; /// @notice Accepts user deposits and mints rETH; handles assignment of deposited ETH to minipools contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaultWithdrawerInterface { - // Libs - using SafeMath for uint256; - using SignedSafeMath for int256; - using SafeCast for uint256; - // Immutables RocketVaultInterface immutable rocketVault; RocketTokenRETHInterface immutable rocketTokenRETH; @@ -69,7 +63,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul /// @notice Returns the user owned portion of the deposit pool (negative indicates more ETH has been "lent" to the /// deposit pool by node operators in the queue than is available from user deposits) function getUserBalance() override public view returns (int256) { - return getBalance().toInt256().sub(getNodeBalance().toInt256()); + return int256(getBalance()) - int256(getNodeBalance()); } /// @notice Excess deposit pool balance (in excess of minipool queue capacity) @@ -80,7 +74,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul uint256 balance = getBalance(); // Calculate and return if (minipoolCapacity >= balance) { return 0; } - else { return balance.sub(minipoolCapacity); } + else { return balance - minipoolCapacity; } } /// @dev Callback required to receive ETH withdrawal from the vault @@ -102,22 +96,22 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul maxCapacity = maximumDepositPoolSize + queueEffectiveCapacity assert(capacityNeeded <= maxCapacity) */ - uint256 capacityNeeded = getBalance().add(msg.value); + uint256 capacityNeeded = getBalance() + msg.value; uint256 maxDepositPoolSize = rocketDAOProtocolSettingsDeposit.getMaximumDepositPoolSize(); if (capacityNeeded > maxDepositPoolSize) { // Doing a conditional require() instead of a single one optimises for the common // case where capacityNeeded fits in the deposit pool without looking at the queue if (rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled()) { RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")); - require(capacityNeeded <= maxDepositPoolSize.add(rocketMinipoolQueue.getEffectiveCapacity()), + require(capacityNeeded <= maxDepositPoolSize + rocketMinipoolQueue.getEffectiveCapacity(), "The deposit pool size after depositing (and matching with minipools) exceeds the maximum size"); } else { revert("The deposit pool size after depositing exceeds the maximum size"); } } // Calculate deposit fee - uint256 depositFee = msg.value.mul(rocketDAOProtocolSettingsDeposit.getDepositFee()).div(calcBase); - uint256 depositNet = msg.value.sub(depositFee); + uint256 depositFee = msg.value * rocketDAOProtocolSettingsDeposit.getDepositFee() / calcBase; + uint256 depositNet = msg.value - depositFee; // Mint rETH to user account rocketTokenRETH.mint(depositNet, msg.sender); // Emit deposit received event @@ -138,13 +132,13 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // When assignments are enabled, we can accept the max amount plus whatever space is available in the minipool queue if (rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled()) { RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")); - maxCapacity = maxCapacity.add(rocketMinipoolQueue.getEffectiveCapacity()); + maxCapacity = maxCapacity + rocketMinipoolQueue.getEffectiveCapacity(); } // Check we aren't already over if (depositPoolBalance >= maxCapacity) { return 0; } - return maxCapacity.sub(depositPoolBalance); + return maxCapacity - depositPoolBalance; } /// @dev Accepts ETH deposit from the node deposit contract (does not mint rETH) @@ -242,9 +236,9 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul // Calculate the number of minipools to assign uint256 maxAssignments = _rocketDAOProtocolSettingsDeposit.getMaximumDepositAssignments(); uint256 variableDepositAmount = rocketDAOProtocolSettingsMinipool.getVariableDepositAmount(); - uint256 scalingCount = msg.value.div(variableDepositAmount); - uint256 totalEthCount = getBalance().div(variableDepositAmount); - uint256 assignments = _rocketDAOProtocolSettingsDeposit.getMaximumDepositSocialisedAssignments().add(scalingCount); + uint256 scalingCount = msg.value / variableDepositAmount; + uint256 totalEthCount = getBalance() / variableDepositAmount; + uint256 assignments = _rocketDAOProtocolSettingsDeposit.getMaximumDepositSocialisedAssignments() + scalingCount; if (assignments > totalEthCount) { assignments = totalEthCount; } @@ -254,7 +248,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul address[] memory minipools = _rocketMinipoolQueue.dequeueMinipools(assignments); if (minipools.length > 0){ // Withdraw ETH from vault - uint256 totalEther = minipools.length.mul(variableDepositAmount); + uint256 totalEther = minipools.length / variableDepositAmount; rocketVault.withdrawEther(totalEther); uint256 nodeBalanceUsed = 0; // Loop over minipools and deposit the amount required to reach launch balance @@ -262,7 +256,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul RocketMinipoolInterface minipool = RocketMinipoolInterface(minipools[i]); // Assign deposit to minipool minipool.deposit{value: variableDepositAmount}(); - nodeBalanceUsed = nodeBalanceUsed.add(minipool.getNodeTopUpValue()); + nodeBalanceUsed = nodeBalanceUsed + minipool.getNodeTopUpValue(); // Emit deposit assigned event emit DepositAssigned(minipools[i], variableDepositAmount, block.timestamp); } @@ -293,11 +287,11 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul minipoolCapacity = rocketDAOProtocolSettingsMinipool.getDepositUserAmount(depositType); } count--; - if (minipoolCapacity == 0 || balance.sub(totalEther) < minipoolCapacity) { break; } + if (minipoolCapacity == 0 || balance - totalEther < minipoolCapacity) { break; } // Dequeue the minipool address minipoolAddress = _rocketMinipoolQueue.dequeueMinipoolByDepositLegacy(depositType); // Update running total - totalEther = totalEther.add(minipoolCapacity); + totalEther = totalEther + minipoolCapacity; // Add assignment assignments[i].etherAssigned = minipoolCapacity; assignments[i].minipoolAddress = minipoolAddress; @@ -331,4 +325,47 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul emit ExcessWithdrawn(msg.sender, _amount, block.timestamp); } + /// @notice Requests + function requestFunds(uint256 validatorId, uint256 amount, bool _expressQueue) external payable onlyRegisteredMegapool(msg.sender) { + LinkedListStorageInterface linkedListStorage = LinkedListStorageInterface(getContractAddress("linkedListStorage")); + + address nodeAddress = RocketMegapoolInterface(msg.sender).getNodeAddress(); + //uint256 ethSupplied = RocketNodeManagerInterface.getEthSupplied(nodeAddress) + msg.value; + //uint256 ethMatched = RocketNodeManagerInterface.getEthMatched(nodeAddress) + amount; + + // Check that the ratio of ethSupplied and ethMatched meets minimum requirement + // If useExpressTicket, subtract an express ticket + + DepositQueueValue memory value = DepositQueueValue({ + receiver: msg.sender, // Megapool address + validatorId: uint32(validatorId), // Incrementing id per validator in a megapool + suppliedValue: uint32(msg.value / 10 ** 15), // NO bond amount + requestedValue: uint32(amount / 10 ** 15) // Amount being requested + }); + + bytes32 namespace; + if (_expressQueue) { + namespace = keccak256("deposit.queue.express"); + } else { + namespace = keccak256("deposit.queue.standard"); + } + linkedListStorage.enqueueItem(namespace, value); + } + + function exitQueue(uint256 _validatorId, bool _expressQueue) external onlyRegisteredMegapool(msg.sender) { + LinkedListStorageInterface linkedListStorage = LinkedListStorageInterface(getContractAddress("linkedListStorage")); + DepositQueueValue memory key = DepositQueueValue({ + receiver: msg.sender, + validatorId: uint32(_validatorId), + suppliedValue: 0, + requestedValue: 0 + }); + bytes32 namespace; + if (_expressQueue) { + namespace = keccak256("deposit.queue.express"); + } else { + namespace = keccak256("deposit.queue.standard"); + } + linkedListStorage.removeItem(namespace, key); + } } diff --git a/contracts/contract/helper/RocketNodeDepositLEB4.sol b/contracts/contract/helper/RocketNodeDepositLEB4.sol deleted file mode 100644 index e877fda70..000000000 --- a/contracts/contract/helper/RocketNodeDepositLEB4.sol +++ /dev/null @@ -1,360 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity 0.8.18; - -import "../RocketBase.sol"; -import "../../interface/deposit/RocketDepositPoolInterface.sol"; -import "../../interface/minipool/RocketMinipoolInterface.sol"; -import "../../interface/minipool/RocketMinipoolManagerInterface.sol"; -import "../../interface/minipool/RocketMinipoolQueueInterface.sol"; -import "../../interface/network/RocketNetworkFeesInterface.sol"; -import "../../interface/node/RocketNodeDepositInterface.sol"; -import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsDepositInterface.sol"; -import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol"; -import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol"; -import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNetworkInterface.sol"; -import "../../interface/dao/node/RocketDAONodeTrustedInterface.sol"; -import "../../interface/dao/node/settings/RocketDAONodeTrustedSettingsMembersInterface.sol"; -import "../../types/MinipoolDeposit.sol"; -import "../../interface/node/RocketNodeManagerInterface.sol"; -import "../../interface/RocketVaultInterface.sol"; -import "../../interface/node/RocketNodeStakingInterface.sol"; -import "../network/RocketNetworkSnapshots.sol"; - -/// @dev NOT USED IN PRODUCTION - This contract only exists to test future functionality that may or may not be included -/// in a future Rocket Pool release -contract RocketNodeDepositLEB4 is RocketBase, RocketNodeDepositInterface { - - // Events - event DepositReceived(address indexed from, uint256 amount, uint256 time); - event DepositFor(address indexed nodeAddress, address indexed from, uint256 amount, uint256 time); - event Withdrawal(address indexed nodeAddress, address indexed to, uint256 amount, uint256 time); - - constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) { - version = 3; - } - - /// @dev Accept incoming ETH from the deposit pool - receive() external payable onlyLatestContract("rocketDepositPool", msg.sender) {} - - - /// @notice Returns a node operator's credit balance in wei - function getNodeDepositCredit(address _nodeOperator) override public view returns (uint256) { - return getUint(keccak256(abi.encodePacked("node.deposit.credit.balance", _nodeOperator))); - } - - /// @notice Returns the current ETH balance for the given node operator - function getNodeEthBalance(address _nodeAddress) override public view returns (uint256) { - return getUint(keccak256(abi.encodePacked("node.eth.balance", _nodeAddress))); - } - - /// @notice Returns the sum of the credit balance of a given node operator and their balance - function getNodeCreditAndBalance(address _nodeAddress) override external view returns (uint256) { - return getNodeDepositCredit(_nodeAddress) + getNodeEthBalance(_nodeAddress); - } - - /// @notice Returns the sum of the amount of ETH credit currently usable by a given node operator and their balance - function getNodeUsableCreditAndBalance(address _nodeAddress) override external view returns (uint256) { - return getNodeUsableCredit(_nodeAddress) + getNodeEthBalance(_nodeAddress); - } - - /// @notice Returns the amount of ETH credit currently usable by a given node operator - function getNodeUsableCredit(address _nodeAddress) override public view returns (uint256) { - RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); - uint256 depositPoolBalance = rocketDepositPool.getBalance(); - uint256 usableCredit = getNodeDepositCredit(_nodeAddress); - if (usableCredit > depositPoolBalance) { - usableCredit = depositPoolBalance; - } - return usableCredit; - } - - /// @dev Increases a node operators deposit credit balance - function increaseDepositCreditBalance(address _nodeOperator, uint256 _amount) override external onlyLatestContract("rocketNodeDeposit", address(this)) { - // Accept calls from network contracts or registered minipools - require(getBool(keccak256(abi.encodePacked("minipool.exists", msg.sender))) || - getBool(keccak256(abi.encodePacked("contract.exists", msg.sender))), - "Invalid or outdated network contract"); - // Increase credit balance - addUint(keccak256(abi.encodePacked("node.deposit.credit.balance", _nodeOperator)), _amount); - } - - /// @notice Deposits ETH for the given node operator - /// @param _nodeAddress The address of the node operator to deposit ETH for - function depositEthFor(address _nodeAddress) override external payable onlyRegisteredMinipool(_nodeAddress) { - // Send the ETH to vault - uint256 amount = msg.value; - RocketVaultInterface rocketVault = RocketVaultInterface(getContractAddress("rocketVault")); - rocketVault.depositEther{value: amount}(); - // Increment balance - addUint(keccak256(abi.encodePacked("node.eth.balance", _nodeAddress)), amount); - // Log it - emit DepositFor(_nodeAddress, msg.sender, amount, block.timestamp); - } - - /// @notice Withdraws ETH from a node operator's balance. Must be called from withdrawal address. - /// @param _nodeAddress Address of the node operator to withdraw from - /// @param _amount Amount of ETH to withdraw - function withdrawEth(address _nodeAddress, uint256 _amount) external onlyRegisteredMinipool(_nodeAddress) { - // Check valid caller - address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(_nodeAddress); - require(msg.sender == withdrawalAddress, "Only withdrawal address can withdraw ETH"); - // Check balance and update - uint256 balance = getNodeEthBalance(_nodeAddress); - require(balance >= _amount, "Insufficient balance"); - setUint(keccak256(abi.encodePacked("node.eth.balance", _nodeAddress)), balance - _amount); - // Withdraw the funds - RocketVaultInterface rocketVault = RocketVaultInterface(getContractAddress("rocketVault")); - rocketVault.withdrawEther(_amount); - // Send funds to withdrawalAddress - (bool success, ) = withdrawalAddress.call{value: _amount}(""); - require(success, "Failed to withdraw ETH"); - // Log it - emit DepositFor(_nodeAddress, withdrawalAddress, _amount, block.timestamp); - } - - - /// @notice Accept a node deposit and create a new minipool under the node. Only accepts calls from registered nodes - /// @param _bondAmount The amount of capital the node operator wants to put up as his bond - /// @param _minimumNodeFee Transaction will revert if network commission rate drops below this amount - /// @param _validatorPubkey Pubkey of the validator the node operator wishes to migrate - /// @param _validatorSignature Signature from the validator over the deposit data - /// @param _depositDataRoot The hash tree root of the deposit data (passed onto the deposit contract on pre stake) - /// @param _salt Salt used to deterministically construct the minipool's address - /// @param _expectedMinipoolAddress The expected deterministic minipool address. Will revert if it doesn't match - function deposit(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) { - // Check amount - require(msg.value == _bondAmount, "Invalid value"); - // Process the deposit - _deposit(_bondAmount, _minimumNodeFee, _validatorPubkey, _validatorSignature, _depositDataRoot, _salt, _expectedMinipoolAddress); - } - - /// @notice Accept a node deposit and create a new minipool under the node. Only accepts calls from registered nodes - /// @param _bondAmount The amount of capital the node operator wants to put up as his bond - /// @param _minimumNodeFee Transaction will revert if network commission rate drops below this amount - /// @param _validatorPubkey Pubkey of the validator the node operator wishes to migrate - /// @param _validatorSignature Signature from the validator over the deposit data - /// @param _depositDataRoot The hash tree root of the deposit data (passed onto the deposit contract on pre stake) - /// @param _salt Salt used to deterministically construct the minipool's address - /// @param _expectedMinipoolAddress The expected deterministic minipool address. Will revert if it doesn't match - function depositWithCredit(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) { - { - uint256 balanceToUse = 0; - uint256 creditToUse = 0; - uint256 shortFall = _bondAmount - msg.value; - uint256 credit = getNodeUsableCredit(msg.sender); - uint256 balance = getNodeEthBalance(msg.sender); - // Check credit - require (credit + balance >= shortFall, "Insufficient credit"); - // Calculate amounts to use - creditToUse = shortFall; - if (credit < shortFall) { - balanceToUse = shortFall - credit; - creditToUse = credit; - } - // Update balances - if (balanceToUse > 0) { - subUint(keccak256(abi.encodePacked("node.eth.balance", msg.sender)), balanceToUse); - // Withdraw the funds - RocketVaultInterface rocketVault = RocketVaultInterface(getContractAddress("rocketVault")); - rocketVault.withdrawEther(balanceToUse); - } - if (creditToUse > 0) { - subUint(keccak256(abi.encodePacked("node.deposit.credit.balance", msg.sender)), creditToUse); - } - } - // Process the deposit - _deposit(_bondAmount, _minimumNodeFee, _validatorPubkey, _validatorSignature, _depositDataRoot, _salt, _expectedMinipoolAddress); - } - - /// @notice Returns true if the given amount is a valid deposit amount - function isValidDepositAmount(uint256 _amount) override public pure returns (bool) { - return _amount == 16 ether || _amount == 8 ether || _amount == 4 ether; - } - - /// @notice Returns an array of valid deposit amounts - function getDepositAmounts() override external pure returns (uint256[] memory) { - uint256[] memory amounts = new uint256[](3); - amounts[0] = 16 ether; - amounts[1] = 8 ether; - amounts[2] = 4 ether; - return amounts; - } - - /// @dev Internal logic to process a deposit - function _deposit(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) private { - // Check pre-conditions - checkDepositsEnabled(); - checkDistributorInitialised(); - checkNodeFee(_minimumNodeFee); - require(isValidDepositAmount(_bondAmount), "Invalid deposit amount"); - // Get launch constants - uint256 launchAmount; - uint256 preLaunchValue; - { - RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool")); - launchAmount = rocketDAOProtocolSettingsMinipool.getLaunchBalance(); - preLaunchValue = rocketDAOProtocolSettingsMinipool.getPreLaunchValue(); - } - // Check that pre deposit won't fail - if (msg.value < preLaunchValue) { - RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); - require(preLaunchValue- msg.value <= rocketDepositPool.getBalance(), "Deposit pool balance is insufficient for pre deposit"); - } - // Emit deposit received event - emit DepositReceived(msg.sender, msg.value, block.timestamp); - // Increase ETH matched (used to calculate RPL collateral requirements) - _increaseEthMatched(msg.sender, launchAmount- _bondAmount); - // Create the minipool - RocketMinipoolInterface minipool = createMinipool(_salt, _expectedMinipoolAddress); - // Process node deposit - _processNodeDeposit(preLaunchValue, _bondAmount); - // Perform the pre deposit - minipool.preDeposit{value: preLaunchValue}(_bondAmount, _validatorPubkey, _validatorSignature, _depositDataRoot); - // Enqueue the minipool - enqueueMinipool(address(minipool)); - // Assign deposits if enabled - assignDeposits(); - } - - /// @dev Processes a node deposit with the deposit pool - /// @param _preLaunchValue The prelaunch value (result of call to `RocketDAOProtocolSettingsMinipool.getPreLaunchValue()` - /// @param _bondAmount The bond amount for this deposit - function _processNodeDeposit(uint256 _preLaunchValue, uint256 _bondAmount) private { - // Get contracts - RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); - // Retrieve ETH from deposit pool if required - uint256 shortFall = 0; - if (msg.value < _preLaunchValue) { - shortFall = _preLaunchValue- msg.value; - rocketDepositPool.nodeCreditWithdrawal(shortFall); - } - uint256 remaining = msg.value + shortFall - _preLaunchValue; - // Deposit the left over value into the deposit pool - rocketDepositPool.nodeDeposit{value: remaining}(_bondAmount - _preLaunchValue); - } - - /// @notice Creates a "vacant" minipool which a node operator can use to migrate a validator with a BLS withdrawal credential - /// @param _bondAmount The amount of capital the node operator wants to put up as his bond - /// @param _minimumNodeFee Transaction will revert if network commission rate drops below this amount - /// @param _validatorPubkey Pubkey of the validator the node operator wishes to migrate - /// @param _salt Salt used to deterministically construct the minipool's address - /// @param _expectedMinipoolAddress The expected deterministic minipool address. Will revert if it doesn't match - /// @param _currentBalance The current balance of the validator on the beaconchain (will be checked by oDAO and scrubbed if not correct) - function createVacantMinipool(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, uint256 _salt, address _expectedMinipoolAddress, uint256 _currentBalance) override external onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) { - // Check pre-conditions - checkVacantMinipoolsEnabled(); - checkDistributorInitialised(); - checkNodeFee(_minimumNodeFee); - require(isValidDepositAmount(_bondAmount), "Invalid deposit amount"); - // Increase ETH matched (used to calculate RPL collateral requirements) - RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool")); - uint256 launchAmount = rocketDAOProtocolSettingsMinipool.getLaunchBalance(); - _increaseEthMatched(msg.sender, launchAmount - _bondAmount); - // Create the minipool - _createVacantMinipool(_salt, _validatorPubkey, _bondAmount, _expectedMinipoolAddress, _currentBalance); - } - - /// @notice Called by minipools during bond reduction to increase the amount of ETH the node operator has - /// @param _nodeAddress The node operator's address to increase the ETH matched for - /// @param _amount The amount to increase the ETH matched - /// @dev Will revert if the new ETH matched amount exceeds the node operators limit - function increaseEthMatched(address _nodeAddress, uint256 _amount) override external onlyLatestContract("rocketNodeDeposit", address(this)) onlyLatestNetworkContract() { - _increaseEthMatched(_nodeAddress, _amount); - } - - /// @dev Increases the amount of ETH that has been matched against a node operators bond. Reverts if it exceeds the - /// collateralisation requirements of the network - function _increaseEthMatched(address _nodeAddress, uint256 _amount) private { - // Check amount doesn't exceed limits - RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking")); - RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots")); - uint256 ethMatched = rocketNodeStaking.getNodeETHMatched(_nodeAddress) + _amount; - require( - ethMatched <= rocketNodeStaking.getNodeETHMatchedLimit(_nodeAddress), - "ETH matched after deposit exceeds limit based on node RPL stake" - ); - // Push the change to snapshot manager - bytes32 key = keccak256(abi.encodePacked("eth.matched.node.amount", _nodeAddress)); - rocketNetworkSnapshots.push(key, uint224(ethMatched)); - } - - /// @dev Adds a minipool to the queue - function enqueueMinipool(address _minipoolAddress) private { - // Add minipool to queue - RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")).enqueueMinipool(_minipoolAddress); - } - - /// @dev Reverts if node operator has not initialised their fee distributor - function checkDistributorInitialised() private view { - // Check node has initialised their fee distributor - RocketNodeManagerInterface rocketNodeManager = RocketNodeManagerInterface(getContractAddress("rocketNodeManager")); - require(rocketNodeManager.getFeeDistributorInitialised(msg.sender), "Fee distributor not initialised"); - } - - /// @dev Creates a minipool and returns an instance of it - /// @param _salt The salt used to determine the minipools address - /// @param _expectedMinipoolAddress The expected minipool address. Reverts if not correct - function createMinipool(uint256 _salt, address _expectedMinipoolAddress) private returns (RocketMinipoolInterface) { - // Load contracts - RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager")); - // Check minipool doesn't exist or previously exist - require(!rocketMinipoolManager.getMinipoolExists(_expectedMinipoolAddress) && !rocketMinipoolManager.getMinipoolDestroyed(_expectedMinipoolAddress), "Minipool already exists or was previously destroyed"); - // Create minipool - RocketMinipoolInterface minipool = rocketMinipoolManager.createMinipool(msg.sender, _salt); - // Ensure minipool address matches expected - require(address(minipool) == _expectedMinipoolAddress, "Unexpected minipool address"); - // Return - return minipool; - } - - /// @dev Creates a vacant minipool and returns an instance of it - /// @param _salt The salt used to determine the minipools address - /// @param _validatorPubkey Pubkey of the validator owning this minipool - /// @param _bondAmount ETH value the node operator is putting up as capital for this minipool - /// @param _expectedMinipoolAddress The expected minipool address. Reverts if not correct - /// @param _currentBalance The current balance of the validator on the beaconchain (will be checked by oDAO and scrubbed if not correct) - function _createVacantMinipool(uint256 _salt, bytes calldata _validatorPubkey, uint256 _bondAmount, address _expectedMinipoolAddress, uint256 _currentBalance) private returns (RocketMinipoolInterface) { - // Load contracts - RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager")); - // Check minipool doesn't exist or previously exist - require(!rocketMinipoolManager.getMinipoolExists(_expectedMinipoolAddress) && !rocketMinipoolManager.getMinipoolDestroyed(_expectedMinipoolAddress), "Minipool already exists or was previously destroyed"); - // Create minipool - RocketMinipoolInterface minipool = rocketMinipoolManager.createVacantMinipool(msg.sender, _salt, _validatorPubkey, _bondAmount, _currentBalance); - // Ensure minipool address matches expected - require(address(minipool) == _expectedMinipoolAddress, "Unexpected minipool address"); - // Return - return minipool; - } - - /// @dev Reverts if network node fee is below a minimum - /// @param _minimumNodeFee The minimum node fee required to not revert - function checkNodeFee(uint256 _minimumNodeFee) private view { - // Load contracts - RocketNetworkFeesInterface rocketNetworkFees = RocketNetworkFeesInterface(getContractAddress("rocketNetworkFees")); - // Check current node fee - uint256 nodeFee = rocketNetworkFees.getNodeFee(); - require(nodeFee >= _minimumNodeFee, "Minimum node fee exceeds current network node fee"); - } - - /// @dev Reverts if deposits are not enabled - function checkDepositsEnabled() private view { - // Get contracts - RocketDAOProtocolSettingsNodeInterface rocketDAOProtocolSettingsNode = RocketDAOProtocolSettingsNodeInterface(getContractAddress("rocketDAOProtocolSettingsNode")); - // Check node settings - require(rocketDAOProtocolSettingsNode.getDepositEnabled(), "Node deposits are currently disabled"); - } - - /// @dev Reverts if vacant minipools are not enabled - function checkVacantMinipoolsEnabled() private view { - // Get contracts - RocketDAOProtocolSettingsNodeInterface rocketDAOProtocolSettingsNode = RocketDAOProtocolSettingsNodeInterface(getContractAddress("rocketDAOProtocolSettingsNode")); - // Check node settings - require(rocketDAOProtocolSettingsNode.getVacantMinipoolsEnabled(), "Vacant minipools are currently disabled"); - } - - /// @dev Executes an assignDeposits call on the deposit pool - function assignDeposits() private { - RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); - rocketDepositPool.maybeAssignDeposits(); - } -} diff --git a/contracts/contract/megapool/RocketMegapool.sol b/contracts/contract/megapool/RocketMegapool.sol new file mode 100644 index 000000000..0157ffc21 --- /dev/null +++ b/contracts/contract/megapool/RocketMegapool.sol @@ -0,0 +1,268 @@ +pragma solidity >0.5.0 <0.9.0; +pragma abicoder v2; + +// SPDX-License-Identifier: GPL-3.0-only + +import "../RocketBase.sol"; +import "../../interface/RocketVaultInterface.sol"; +import "../../interface/deposit/RocketDepositPoolInterface.sol"; +import "../../interface/casper/DepositInterface.sol"; +import "../../interface/megapool/RocketMegapoolInterface.sol"; +import "../../interface/node/RocketNodeManagerInterface.sol"; +import "../../interface/dao/node/settings/RocketDAONodeTrustedSettingsMinipoolInterface.sol"; +import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMegapoolInterface.sol"; +import "../../interface/token/RocketTokenRETHInterface.sol"; + +enum Status { + InQueue, + PreStaked, + Assigned, + Staking, + Exited, + Dissolved +} + +struct ValidatorInfo { + Status status; + bool express; + uint32 assignmentTime; + uint32 totalScrubVotes; + bytes withdrawalCredential; +} + +/// @title RocketMegapool +/// @notice This contract manages multiple validators. It serves as the target of Beacon Chain withdrawal credentials. +contract RocketMegapool is RocketBase, RocketMegapoolInterface { + + // Construct + constructor(address _nodeAddress, RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) { + version = 1; + require(address(_rocketStorageAddress) != address(0) && _nodeAddress != address(0), "Missing address"); + // Precompute keys + rocketVaultKey = keccak256(abi.encodePacked("contract.address", "rocketVault")); + rocketTokenRPLKey = keccak256(abi.encodePacked("contract.address", "rocketTokenRPL")); + rocketTokenRETH = payable(getContractAddress("rocketTokenRETH")); + nodeAddress = _nodeAddress; + } + + mapping(uint32 => ValidatorInfo) validators; + mapping(uint256=> mapping(address=>bool)) memberScrubVotes; // validatorId => member address => voted + + // Constants + uint256 constant pubKeyLength = 48; + uint256 constant signatureLength = 96; + + // Events + event MegapoolValidatorEnqueued(address indexed megapool, uint256 indexed validatorId, uint256 time); + event MegapoolValidatorDequeued(address indexed megapool, uint256 indexed validatorId, uint256 time); + + + // Immutables + address immutable nodeAddress; + bytes32 immutable rocketTokenRPLKey; + bytes32 immutable rocketVaultKey; + address payable immutable rocketTokenRETH; + + uint256 numValidators; + uint256 assignedValue; + uint256 debt; + uint256 stakedRPL; + uint256 unstakedRPL; + uint256 lastUnstakeRequest; + + + // Modifiers + modifier onlyMegapoolOwner() { + address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(nodeAddress); + require(msg.sender == nodeAddress || msg.sender == withdrawalAddress, "Only the node operator can access this method"); + _; + } + + modifier onlyRPLWithdrawalAddressOrNode() { + // Check that the call is coming from RPL withdrawal address (or node if unset) + RocketNodeManagerInterface rocketNodeManager = RocketNodeManagerInterface(getContractAddress("rocketNodeManager")); + if (rocketNodeManager.getNodeRPLWithdrawalAddressIsSet(nodeAddress)) { + address rplWithdrawalAddress = rocketNodeManager.getNodeRPLWithdrawalAddress(nodeAddress); + require(msg.sender == rplWithdrawalAddress, "Must be called from RPL withdrawal address"); + } else { + require(msg.sender == nodeAddress, "Must be called from node address"); + } + _; + } + + /// @notice Creates a new validator as part of a megapool + /// @param bondAmount the amount being bonded by the Node Operator for the new validator + /// @param useExpressTicket if an express ticket should be used + function newValidator(uint256 bondAmount, bool useExpressTicket) external payable onlyLatestContract("rocketNodeDeposit", msg.sender) { + require(msg.value == bondAmount, "Invalid value"); + RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); + uint32 validatorId = uint32(numValidators); + numValidators++; + + validators[validatorId].status = Status.InQueue; + validators[validatorId].express = useExpressTicket; + + rocketDepositPool.requestFunds{value: msg.value}(validatorId, 32 ether, useExpressTicket); + + emit MegapoolValidatorEnqueued(address(this), validatorId, block.timestamp); + } + + /// @notice removes a validator from the deposit queue + /// @param validatorId the validator ID + function dequeue(uint32 validatorId) external onlyMegapoolOwner() { + require(validators[validatorId].status == Status.InQueue, "Validator must be in queue"); + RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); + rocketDepositPool.exitQueue(validatorId, validators[validatorId].express); + validators[validatorId].status = Status.Exited; + + emit MegapoolValidatorDequeued(address(this), validatorId, block.timestamp); + } + + /// @notice + /// @param validatorId the validator ID + function assignFunds(uint32 validatorId) external payable onlyLatestContract("rocketDepositPool", msg.sender) { + validators[validatorId].status = Status.Assigned; + assignedValue += msg.value; + validators[validatorId].assignmentTime = uint32(block.timestamp); + } + + /// @notice Executes the first 1 ETH deposit on the Beacon Chain + /// @param validatorId the validator ID + /// @param pubKey the validator pubKey + /// @param withdrawalCredentials the withdrawal credentials that are going to be used by the validator + /// @param signature The signature from the validator of the deposit data + /// @param depositDataRoot The hash tree root of the deposit data + function preStake(uint32 validatorId, bytes calldata pubKey, bytes calldata withdrawalCredentials, bytes calldata signature, bytes32 depositDataRoot) external onlyMegapoolOwner() { + validateBytes(pubKey, pubKeyLength); + validateBytes(signature, signatureLength); + + validators[validatorId].status = Status.PreStaked; + DepositInterface casperDeposit = DepositInterface(getContractAddress("casperDeposit")); + + assignedValue -= 1 ether; + // Perform the 1 ETH prestake + casperDeposit.deposit{value : 1 ether}(pubKey, withdrawalCredentials, signature, depositDataRoot); + } + + /// @notice performs the remaining ETH deposit on the Beacon Chain + /// @param validatorId the validator ID + /// @param pubKey ' + /// @param signature ' + /// @param withdrawalCredentialStateProof ' + function stake(uint32 validatorId, bytes calldata pubKey, bytes calldata signature, bytes32 depositDataRoot, StateProof calldata withdrawalCredentialStateProof) external onlyMegapoolOwner() { + validateBytes(pubKey, pubKeyLength); + validateBytes(signature, signatureLength); + require(validators[validatorId].status == Status.PreStaked, "Invalid status"); + + // Get scrub period + RocketDAONodeTrustedSettingsMinipoolInterface rocketDAONodeTrustedSettingsMinipool = RocketDAONodeTrustedSettingsMinipoolInterface(getContractAddress("rocketDAONodeTrustedSettingsMinipool")); + uint256 scrubPeriod = rocketDAONodeTrustedSettingsMinipool.getScrubPeriod(); + require(block.timestamp > validators[validatorId].assignmentTime + scrubPeriod, "Not past the scrub period"); + + validators[validatorId].status = Status.Staking; + DepositInterface casperDeposit = DepositInterface(getContractAddress("casperDeposit")); + assignedValue -= 31 ether; + bytes memory withdrawalCredentials = validators[validatorId].withdrawalCredential; + + // TODO: Verify state proof to ensure validator has correct withdrawal credentials + + // Perform remaining 31 ETH stake onto beaconchain + casperDeposit.deposit{value : 31 ether}(pubKey, withdrawalCredentials, signature, depositDataRoot); + + } + + /// @notice Dissolves a validator + /// @param validatorId the validator ID + function dissolveValidator(uint32 validatorId) external { + ValidatorInfo storage validator = validators[validatorId]; + // Check current status + require(validator.status == Status.PreStaked, "The validator can only be dissolved while in PreStaked status"); + + //require(block.timestamp > validator.assignmentTime + getTimeBeforeDissolved(), "Cannot dissolve the validator"); + + // Exit the validator from the queue + RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); + rocketDepositPool.exitQueue(validatorId, validator.express); + + // Update the status to dissolved + validator.status = Status.Dissolved; + + // TODO: Perform the dissolution + + } + + /// @notice Receives ETH, which is sent to the rETH contract, to repay a Megapool debt + function repayDebt() external payable { + require(msg.value > 0, "Invalid value received"); + require(debt >= msg.value, "Not enough debt"); + + (bool success, ) = rocketTokenRETH.call{value: msg.value}(""); + require(success, "Failed to send ETH to the rETH contract"); + + // Should not revert as debt >= msg.value + unchecked { + debt -= msg.value; + } + } + + /// @notice stakes RPL on the megapool + /// @param _amount the RPL amount to be staked on this megapool + function stakeRPL(uint256 _amount) external { + require(_amount > 0, "Invalid amount"); + + address rplTokenAddress = getAddress(rocketTokenRPLKey); + IERC20 rplToken = IERC20(rplTokenAddress); + + // Transfer RPL tokens + require(rplToken.transferFrom(msg.sender, address(this), _amount), "Could not transfer RPL to staking contract"); + stakedRPL += _amount; + + } + + /// @notice requests RPL previously staked on this megapool to be unstaked + // @param _amount the RPL amount to be unstaked + function requestUnstakeRPL(uint256 _amount) external onlyRPLWithdrawalAddressOrNode() { + lastUnstakeRequest = block.timestamp; + + require(_amount > 0 && _amount >= stakedRPL , "Invalid amount"); + + stakedRPL -= _amount; + unstakedRPL += _amount; + } + + /// @notice unstakes RPL after waiting the 'unstaking period' after the last unstake request + function unstakeRPL() external onlyRPLWithdrawalAddressOrNode() { + RocketDAOProtocolSettingsMegapoolInterface protocolSettingsMegapool = RocketDAOProtocolSettingsMegapoolInterface(getContractAddress("rocketDAOProtocolSettingsMegapool")); + uint256 unstakingPeriod = protocolSettingsMegapool.getUnstakingPeriod(); + require(lastUnstakeRequest + unstakingPeriod >= block.timestamp, "Not enough time passed since last unstake RPL request"); + address rplWithdrawalAddress; + + RocketNodeManagerInterface rocketNodeManager = RocketNodeManagerInterface(getContractAddress("rocketNodeManager")); + if (rocketNodeManager.getNodeRPLWithdrawalAddressIsSet(nodeAddress)) { + rplWithdrawalAddress = rocketNodeManager.getNodeRPLWithdrawalAddress(nodeAddress); + } else { + rplWithdrawalAddress = nodeAddress; + } + + address rplAddress = getAddress(rocketTokenRPLKey); + + uint256 unstakedAmount = unstakedRPL; + unstakedRPL = 0; + + RocketVaultInterface rocketVault = RocketVaultInterface(getAddress(rocketVaultKey)); + rocketVault.withdrawToken(rplWithdrawalAddress, IERC20(rplAddress), unstakedAmount); + } + + /// @notice Gets the Node address associated to this megapool + function getNodeAddress() public view returns (address) { + return nodeAddress; + } + + /// @notice validates that a byte array has the expected length + /// @param data the byte array being validated + /// @param _length the expected length + function validateBytes(bytes memory data, uint256 _length) pure internal { + require(data.length == _length, "Invalid bytes length"); + } + +} \ No newline at end of file diff --git a/contracts/contract/megapool/RocketMegapoolFactory.sol b/contracts/contract/megapool/RocketMegapoolFactory.sol new file mode 100644 index 000000000..2a9bb5f23 --- /dev/null +++ b/contracts/contract/megapool/RocketMegapoolFactory.sol @@ -0,0 +1,43 @@ +pragma solidity 0.8.18; + +// SPDX-License-Identifier: GPL-3.0-only + +import "../RocketBase.sol"; +import "./RocketMegapool.sol"; +import "../../interface/megapool/RocketMegapoolFactoryInterface.sol"; + +contract RocketMegapoolFactory is RocketBase, RocketMegapoolFactoryInterface { + // Events + event ProxyCreated(address _address); + + // Construct + constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) { + version = 1; + } + + /// @notice Gets the proxy bytecode + function getProxyBytecode() override public pure returns (bytes memory) { + return type(RocketMegapool).creationCode; + } + + /// @notice Calculates the predetermined Megapool contract address from given node address + /// @param _nodeAddress address of the node associated to the megapool + function getProxyAddress(address _nodeAddress) override external view returns(address) { + bytes memory contractCode = getProxyBytecode(); + bytes memory initCode = abi.encodePacked(contractCode, abi.encode(_nodeAddress, rocketStorage)); + + bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), uint256(0), keccak256(initCode))); + + return address(uint160(uint(hash))); + } + + /// @notice Uses CREATE2 to deploy a RocketMegapool at predetermined address + /// @param _nodeAddress address of the node associated to the megapool + function createProxy(address _nodeAddress) override external onlyLatestContract("rocketNodeManager", msg.sender) returns (address) { + // Salt is not required as the initCode is already unique per node address (node address is constructor argument) + RocketMegapool megapool = new RocketMegapool{salt: ''}(_nodeAddress, rocketStorage); + setBool(keccak256(abi.encodePacked("megapool.exists", address(megapool))), true); + emit ProxyCreated(address(megapool)); + return address(megapool); + } +} diff --git a/contracts/contract/node/RocketNodeDeposit.sol b/contracts/contract/node/RocketNodeDeposit.sol index 799c4edf1..bbeaaa00e 100644 --- a/contracts/contract/node/RocketNodeDeposit.sol +++ b/contracts/contract/node/RocketNodeDeposit.sol @@ -18,26 +18,25 @@ import "../../types/MinipoolDeposit.sol"; import "../../interface/node/RocketNodeManagerInterface.sol"; import "../../interface/RocketVaultInterface.sol"; import "../../interface/node/RocketNodeStakingInterface.sol"; +import "../../interface/megapool/RocketMegapoolInterface.sol"; import "../network/RocketNetworkSnapshots.sol"; -import "../../interface/RocketVaultWithdrawerInterface.sol"; -/// @notice Handles node deposits and minipool creation -contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaultWithdrawerInterface { +/// @notice +contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface { // Events event DepositReceived(address indexed from, uint256 amount, uint256 time); event DepositFor(address indexed nodeAddress, address indexed from, uint256 amount, uint256 time); event Withdrawal(address indexed nodeAddress, address indexed to, uint256 amount, uint256 time); - function receiveVaultWithdrawalETH() external payable {} - constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) { - version = 4; + version = 3; } /// @dev Accept incoming ETH from the deposit pool receive() external payable onlyLatestContract("rocketDepositPool", msg.sender) {} + /// @notice Returns a node operator's credit balance in wei function getNodeDepositCredit(address _nodeOperator) override public view returns (uint256) { return getUint(keccak256(abi.encodePacked("node.deposit.credit.balance", _nodeOperator))); @@ -70,20 +69,18 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaul } /// @dev Increases a node operators deposit credit balance - function increaseDepositCreditBalance(address _nodeAddress, uint256 _amount) override external onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(_nodeAddress) { + function increaseDepositCreditBalance(address _nodeOperator, uint256 _amount) override external onlyLatestContract("rocketNodeDeposit", address(this)) { // Accept calls from network contracts or registered minipools require(getBool(keccak256(abi.encodePacked("minipool.exists", msg.sender))) || - getBool(keccak256(abi.encodePacked("contract.exists", msg.sender))), + getBool(keccak256(abi.encodePacked("contract.exists", msg.sender))), "Invalid or outdated network contract"); // Increase credit balance - addUint(keccak256(abi.encodePacked("node.deposit.credit.balance", _nodeAddress)), _amount); + addUint(keccak256(abi.encodePacked("node.deposit.credit.balance", _nodeOperator)), _amount); } /// @notice Deposits ETH for the given node operator /// @param _nodeAddress The address of the node operator to deposit ETH for - function depositEthFor(address _nodeAddress) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(_nodeAddress) { - // Sanity check caller is not node itself - require(msg.sender != _nodeAddress, "Cannot deposit ETH for self"); + function depositEthFor(address _nodeAddress) override external payable onlyRegisteredMinipool(_nodeAddress) { // Send the ETH to vault uint256 amount = msg.value; RocketVaultInterface rocketVault = RocketVaultInterface(getContractAddress("rocketVault")); @@ -97,7 +94,7 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaul /// @notice Withdraws ETH from a node operator's balance. Must be called from withdrawal address. /// @param _nodeAddress Address of the node operator to withdraw from /// @param _amount Amount of ETH to withdraw - function withdrawEth(address _nodeAddress, uint256 _amount) external onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(_nodeAddress) { + function withdrawEth(address _nodeAddress, uint256 _amount) external onlyRegisteredMinipool(_nodeAddress) { // Check valid caller address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(_nodeAddress); require(msg.sender == withdrawalAddress, "Only withdrawal address can withdraw ETH"); @@ -112,36 +109,30 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaul (bool success, ) = withdrawalAddress.call{value: _amount}(""); require(success, "Failed to withdraw ETH"); // Log it - emit Withdrawal(_nodeAddress, withdrawalAddress, _amount, block.timestamp); + emit DepositFor(_nodeAddress, withdrawalAddress, _amount, block.timestamp); } + /// @notice Accept a node deposit and create a new minipool under the node. Only accepts calls from registered nodes /// @param _bondAmount The amount of capital the node operator wants to put up as his bond - /// @param _minimumNodeFee Transaction will revert if network commission rate drops below this amount + /// @param _useExpressTicket If the express queue should be used /// @param _validatorPubkey Pubkey of the validator the node operator wishes to migrate /// @param _validatorSignature Signature from the validator over the deposit data /// @param _depositDataRoot The hash tree root of the deposit data (passed onto the deposit contract on pre stake) - /// @param _salt Salt used to deterministically construct the minipool's address - /// @param _expectedMinipoolAddress The expected deterministic minipool address. Will revert if it doesn't match - function deposit(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) { + function deposit(uint256 _bondAmount, bool _useExpressTicket, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) { // Check amount require(msg.value == _bondAmount, "Invalid value"); // Process the deposit - _deposit(_bondAmount, _minimumNodeFee, _validatorPubkey, _validatorSignature, _depositDataRoot, _salt, _expectedMinipoolAddress); + _deposit(_bondAmount, _useExpressTicket, _validatorPubkey, _validatorSignature, _depositDataRoot); } - /// @notice Accept a node deposit and create a new minipool under the node. Uses node's credit balance to cover - /// shortfall in value provided to cover bond. Only accepts calls from registered nodes + /// @notice Accept a node deposit and create a new minipool under the node. Only accepts calls from registered nodes /// @param _bondAmount The amount of capital the node operator wants to put up as his bond - /// @param _minimumNodeFee Transaction will revert if network commission rate drops below this amount + /// @param _useExpressTicket If the express queue should be used /// @param _validatorPubkey Pubkey of the validator the node operator wishes to migrate /// @param _validatorSignature Signature from the validator over the deposit data /// @param _depositDataRoot The hash tree root of the deposit data (passed onto the deposit contract on pre stake) - /// @param _salt Salt used to deterministically construct the minipool's address - /// @param _expectedMinipoolAddress The expected deterministic minipool address. Will revert if it doesn't match - function depositWithCredit(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) { - // Sanity check - require(msg.value <= _bondAmount, "Excessive value for requested bond"); + function depositWithCredit(uint256 _bondAmount, bool _useExpressTicket, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) { { uint256 balanceToUse = 0; uint256 creditToUse = 0; @@ -168,28 +159,28 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaul } } // Process the deposit - _deposit(_bondAmount, _minimumNodeFee, _validatorPubkey, _validatorSignature, _depositDataRoot, _salt, _expectedMinipoolAddress); + _deposit(_bondAmount, _useExpressTicket, _validatorPubkey, _validatorSignature, _depositDataRoot); } /// @notice Returns true if the given amount is a valid deposit amount function isValidDepositAmount(uint256 _amount) override public pure returns (bool) { - return _amount == 16 ether || _amount == 8 ether; + return _amount == 16 ether || _amount == 8 ether || _amount == 4 ether; } /// @notice Returns an array of valid deposit amounts function getDepositAmounts() override external pure returns (uint256[] memory) { - uint256[] memory amounts = new uint256[](2); + uint256[] memory amounts = new uint256[](3); amounts[0] = 16 ether; amounts[1] = 8 ether; + amounts[2] = 4 ether; return amounts; } /// @dev Internal logic to process a deposit - function _deposit(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) private { + function _deposit(uint256 _bondAmount, bool _useExpressTicket, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot) private { // Check pre-conditions checkDepositsEnabled(); checkDistributorInitialised(); - checkNodeFee(_minimumNodeFee); require(isValidDepositAmount(_bondAmount), "Invalid deposit amount"); // Get launch constants uint256 launchAmount; @@ -199,25 +190,33 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaul launchAmount = rocketDAOProtocolSettingsMinipool.getLaunchBalance(); preLaunchValue = rocketDAOProtocolSettingsMinipool.getPreLaunchValue(); } + // Check that pre deposit won't fail + if (msg.value < preLaunchValue) { + RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); + require(preLaunchValue - msg.value <= rocketDepositPool.getBalance(), "Deposit pool balance is insufficient for pre deposit"); + } // Emit deposit received event emit DepositReceived(msg.sender, msg.value, block.timestamp); // Increase ETH matched (used to calculate RPL collateral requirements) - _increaseEthMatched(msg.sender, launchAmount - _bondAmount); - // Create the minipool - RocketMinipoolInterface minipool = createMinipool(_salt, _expectedMinipoolAddress); + _increaseEthMatched(msg.sender, launchAmount- _bondAmount); + + RocketNodeManagerInterface rocketNodeManager = RocketNodeManagerInterface(getContractAddress("rocketNodeManager")); + + RocketMegapoolInterface megapoolAddress = RocketMegapoolInterface(rocketNodeManager.getMegapoolAddress(msg.sender)); + if (address(megapoolAddress) == address(0)) { + megapoolAddress = RocketMegapoolInterface(rocketNodeManager.deployMegapool(msg.sender)); + } + + megapoolAddress.newValidator(_bondAmount, _useExpressTicket); + // Process node deposit _processNodeDeposit(preLaunchValue, _bondAmount); - // Perform the pre deposit - minipool.preDeposit{value: preLaunchValue}(_bondAmount, _validatorPubkey, _validatorSignature, _depositDataRoot); - // Enqueue the minipool - enqueueMinipool(address(minipool)); + // Assign deposits if enabled assignDeposits(); } - /// @dev Processes a node deposit with the deposit pool. If user has not supplied full bond amount with the transaction - /// the shortfall will be taken from their credit. Any excess ETH after prelaunch value is sent to minipool is - // then deposited into the deposit pool + /// @dev Processes a node deposit with the deposit pool /// @param _preLaunchValue The prelaunch value (result of call to `RocketDAOProtocolSettingsMinipool.getPreLaunchValue()` /// @param _bondAmount The bond amount for this deposit function _processNodeDeposit(uint256 _preLaunchValue, uint256 _bondAmount) private { @@ -225,45 +224,20 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaul RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); // Retrieve ETH from deposit pool if required uint256 shortFall = 0; - if (address(this).balance < _preLaunchValue) { - shortFall = _preLaunchValue - address(this).balance; + if (msg.value < _preLaunchValue) { + shortFall = _preLaunchValue- msg.value; rocketDepositPool.nodeCreditWithdrawal(shortFall); } - uint256 remaining = address(this).balance - _preLaunchValue; + uint256 remaining = msg.value + shortFall - _preLaunchValue; // Deposit the left over value into the deposit pool rocketDepositPool.nodeDeposit{value: remaining}(_bondAmount - _preLaunchValue); } - /// @notice Creates a "vacant" minipool which a node operator can use to migrate a validator with a BLS withdrawal credential - /// @param _bondAmount The amount of capital the node operator wants to put up as his bond - /// @param _minimumNodeFee Transaction will revert if network commission rate drops below this amount - /// @param _validatorPubkey Pubkey of the validator the node operator wishes to migrate - /// @param _salt Salt used to deterministically construct the minipool's address - /// @param _expectedMinipoolAddress The expected deterministic minipool address. Will revert if it doesn't match - /// @param _currentBalance The current balance of the validator on the beaconchain (will be checked by oDAO and scrubbed if not correct) - function createVacantMinipool(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, uint256 _salt, address _expectedMinipoolAddress, uint256 _currentBalance) override external onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) { - // Check pre-conditions - checkVacantMinipoolsEnabled(); - checkDistributorInitialised(); - checkNodeFee(_minimumNodeFee); - require(isValidDepositAmount(_bondAmount), "Invalid deposit amount"); - // Increase ETH matched (used to calculate RPL collateral requirements) - RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool")); - uint256 launchAmount = rocketDAOProtocolSettingsMinipool.getLaunchBalance(); - _increaseEthMatched(msg.sender, launchAmount - _bondAmount); - // Create the minipool - _createVacantMinipool(_salt, _validatorPubkey, _bondAmount, _expectedMinipoolAddress, _currentBalance); - } - /// @notice Called by minipools during bond reduction to increase the amount of ETH the node operator has /// @param _nodeAddress The node operator's address to increase the ETH matched for /// @param _amount The amount to increase the ETH matched /// @dev Will revert if the new ETH matched amount exceeds the node operators limit function increaseEthMatched(address _nodeAddress, uint256 _amount) override external onlyLatestContract("rocketNodeDeposit", address(this)) onlyLatestNetworkContract() { - // Try to distribute any existing rewards at the previous collateral rate - RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager")); - rocketMinipoolManager.tryDistribute(_nodeAddress); - // Increase ETH matched _increaseEthMatched(_nodeAddress, _amount); } @@ -312,25 +286,6 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaul return minipool; } - /// @dev Creates a vacant minipool and returns an instance of it - /// @param _salt The salt used to determine the minipools address - /// @param _validatorPubkey Pubkey of the validator owning this minipool - /// @param _bondAmount ETH value the node operator is putting up as capital for this minipool - /// @param _expectedMinipoolAddress The expected minipool address. Reverts if not correct - /// @param _currentBalance The current balance of the validator on the beaconchain (will be checked by oDAO and scrubbed if not correct) - function _createVacantMinipool(uint256 _salt, bytes calldata _validatorPubkey, uint256 _bondAmount, address _expectedMinipoolAddress, uint256 _currentBalance) private returns (RocketMinipoolInterface) { - // Load contracts - RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager")); - // Check minipool doesn't exist or previously exist - require(!rocketMinipoolManager.getMinipoolExists(_expectedMinipoolAddress) && !rocketMinipoolManager.getMinipoolDestroyed(_expectedMinipoolAddress), "Minipool already exists or was previously destroyed"); - // Create minipool - RocketMinipoolInterface minipool = rocketMinipoolManager.createVacantMinipool(msg.sender, _salt, _validatorPubkey, _bondAmount, _currentBalance); - // Ensure minipool address matches expected - require(address(minipool) == _expectedMinipoolAddress, "Unexpected minipool address"); - // Return - return minipool; - } - /// @dev Reverts if network node fee is below a minimum /// @param _minimumNodeFee The minimum node fee required to not revert function checkNodeFee(uint256 _minimumNodeFee) private view { @@ -349,14 +304,6 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface, RocketVaul require(rocketDAOProtocolSettingsNode.getDepositEnabled(), "Node deposits are currently disabled"); } - /// @dev Reverts if vacant minipools are not enabled - function checkVacantMinipoolsEnabled() private view { - // Get contracts - RocketDAOProtocolSettingsNodeInterface rocketDAOProtocolSettingsNode = RocketDAOProtocolSettingsNodeInterface(getContractAddress("rocketDAOProtocolSettingsNode")); - // Check node settings - require(rocketDAOProtocolSettingsNode.getVacantMinipoolsEnabled(), "Vacant minipools are currently disabled"); - } - /// @dev Executes an assignDeposits call on the deposit pool function assignDeposits() private { RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool")); diff --git a/contracts/contract/node/RocketNodeManager.sol b/contracts/contract/node/RocketNodeManager.sol index b39f612d1..0b8a682bc 100644 --- a/contracts/contract/node/RocketNodeManager.sol +++ b/contracts/contract/node/RocketNodeManager.sol @@ -18,6 +18,8 @@ import "../../interface/node/RocketNodeDepositInterface.sol"; import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol"; import "../../interface/util/IERC20.sol"; import "../../interface/network/RocketNetworkSnapshotsInterface.sol"; +import "../../interface/megapool/RocketMegapoolInterface.sol"; +import "../../interface/megapool/RocketMegapoolFactoryInterface.sol"; /// @notice Node registration and management contract RocketNodeManager is RocketBase, RocketNodeManagerInterface { @@ -454,4 +456,31 @@ contract RocketNodeManager is RocketBase, RocketNodeManagerInterface { } return nodes; } + + /// @notice Deploys a single Megapool contract + /// @param _nodeAddress address of the node associated to the megapool + function deployMegapool(address _nodeAddress) override external returns (address) { + require(getMegapoolAddress(_nodeAddress) == address(0), "Megapool already deployed for this node"); + + RocketMegapoolFactoryInterface rocketMegapool = RocketMegapoolFactoryInterface(getContractAddress("rocketMegapoolFactory")); + return rocketMegapool.createProxy(_nodeAddress); + } + + /// @notice Returns true if node has deployed their Megapool contract + /// @param _nodeAddress address of the node associated to the megapool + function getMegapoolAddress(address _nodeAddress) override public view returns (address) { + // Load contracts + RocketMegapoolFactoryInterface rocketMegapoolFactory = RocketMegapoolFactoryInterface(getContractAddress("rocketMegapoolFactory")); + // Get Megapool address + address contractAddress = rocketMegapoolFactory.getProxyAddress(_nodeAddress); + // Check if contract exists at that address + uint32 codeSize; + assembly { + codeSize := extcodesize(contractAddress) + } + if (codeSize > 0) { + return contractAddress; + } + return address(0); + } } diff --git a/contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsMegapoolInterface.sol b/contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsMegapoolInterface.sol new file mode 100644 index 000000000..e1ebb977f --- /dev/null +++ b/contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsMegapoolInterface.sol @@ -0,0 +1,8 @@ +pragma solidity >0.5.0 <0.9.0; + +// SPDX-License-Identifier: GPL-3.0-only + + +interface RocketDAOProtocolSettingsMegapoolInterface { + function getUnstakingPeriod() external view returns (uint256); +} diff --git a/contracts/interface/deposit/RocketDepositPoolInterface.sol b/contracts/interface/deposit/RocketDepositPoolInterface.sol index 41eb262a5..3288a453c 100644 --- a/contracts/interface/deposit/RocketDepositPoolInterface.sol +++ b/contracts/interface/deposit/RocketDepositPoolInterface.sol @@ -17,4 +17,6 @@ interface RocketDepositPoolInterface { function assignDeposits() external; function maybeAssignDeposits() external returns (bool); function withdrawExcessBalance(uint256 _amount) external; + function requestFunds(uint256 validatorIndex, uint256 amount, bool useExpressTicket) external payable; + function exitQueue(uint256 validatorIndex, bool expressQueue) external; } diff --git a/contracts/interface/megapool/RocketMegapoolFactoryInterface.sol b/contracts/interface/megapool/RocketMegapoolFactoryInterface.sol new file mode 100644 index 000000000..e8b4aa644 --- /dev/null +++ b/contracts/interface/megapool/RocketMegapoolFactoryInterface.sol @@ -0,0 +1,9 @@ +pragma solidity >0.5.0 <0.9.0; + +// SPDX-License-Identifier: GPL-3.0-only + +interface RocketMegapoolFactoryInterface { + function getProxyBytecode() external pure returns (bytes memory); + function getProxyAddress(address _nodeAddress) external view returns(address); + function createProxy(address _nodeAddress) external returns (address); +} diff --git a/contracts/interface/megapool/RocketMegapoolInterface.sol b/contracts/interface/megapool/RocketMegapoolInterface.sol new file mode 100644 index 000000000..7166ae0cc --- /dev/null +++ b/contracts/interface/megapool/RocketMegapoolInterface.sol @@ -0,0 +1,17 @@ +pragma solidity 0.8.18; + +//SPDX-License-Identifier: GPL-3.0-only + +struct StateProof { + bytes data; +} + +interface RocketMegapoolInterface { + + function newValidator(uint256 bondAmount, bool useExpressTicket) external payable; + function dequeue(uint32 validatorId) external; + function assignFunds(uint32 validatorId) external payable; + function preStake(uint32 validatorId, bytes calldata pubKey, bytes calldata withdrawalCredentials, bytes calldata signature, bytes32 depositDataRoot) external; + function stake(uint32 validatorId, bytes calldata pubKey, bytes calldata signature, bytes32 depositDataRoot, StateProof calldata withdrawalCredentialStateProof) external; + function getNodeAddress() external returns (address); +} \ No newline at end of file diff --git a/contracts/interface/node/RocketNodeDepositInterface.sol b/contracts/interface/node/RocketNodeDepositInterface.sol index 2d97b1a7b..51bdeb3c2 100644 --- a/contracts/interface/node/RocketNodeDepositInterface.sol +++ b/contracts/interface/node/RocketNodeDepositInterface.sol @@ -13,10 +13,9 @@ interface RocketNodeDepositInterface { function increaseDepositCreditBalance(address _nodeOperator, uint256 _amount) external; function depositEthFor(address _nodeAddress) external payable; function withdrawEth(address _nodeAddress, uint256 _amount) external; - function deposit(uint256 _depositAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) external payable; - function depositWithCredit(uint256 _depositAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) external payable; + function deposit(uint256 _depositAmount, bool _useExpressTicket, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot) external payable; + function depositWithCredit(uint256 _depositAmount, bool _useExpressTicket, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot) external payable; function isValidDepositAmount(uint256 _amount) external pure returns (bool); function getDepositAmounts() external pure returns (uint256[] memory); - function createVacantMinipool(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, uint256 _salt, address _expectedMinipoolAddress, uint256 _currentBalance) external; function increaseEthMatched(address _nodeAddress, uint256 _amount) external; } diff --git a/contracts/interface/node/RocketNodeManagerInterface.sol b/contracts/interface/node/RocketNodeManagerInterface.sol index 8b0694109..09d52fa91 100644 --- a/contracts/interface/node/RocketNodeManagerInterface.sol +++ b/contracts/interface/node/RocketNodeManagerInterface.sol @@ -39,4 +39,6 @@ interface RocketNodeManagerInterface { function getSmoothingPoolRegisteredNodeCount(uint256 _offset, uint256 _limit) external view returns (uint256); function getNodeDetails(address _nodeAddress) external view returns (NodeDetails memory); function getNodeAddresses(uint256 _offset, uint256 _limit) external view returns (address[] memory); + function deployMegapool(address _nodeAddress) external returns (address); + function getMegapoolAddress(address _nodeAddress) external view returns (address); }