Skip to content

Commit

Permalink
Add upgrade test suite and fix tests to work with megapools
Browse files Browse the repository at this point in the history
  • Loading branch information
kanewallmann committed Oct 31, 2024
1 parent 95ecece commit 7ab961c
Show file tree
Hide file tree
Showing 31 changed files with 746 additions and 922 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "old"]
path = old
url = [email protected]:rocket-pool/rocketpool.git
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity 0.8.18;

import "./RocketDAOProtocolSettings.sol";
import "../../../../interface/dao/protocol/settings/RocketDAOProtocolSettingsDepositInterface.sol";

/// @notice Network deposit settings
contract RocketDAOProtocolSettingsDeposit is RocketDAOProtocolSettings, RocketDAOProtocolSettingsDepositInterface {

Expand Down
64 changes: 47 additions & 17 deletions contracts/contract/deposit/RocketDepositPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@ import "../../interface/util/AddressQueueStorageInterface.sol";
import "../../interface/util/LinkedListStorageInterface.sol";
import "../../types/MinipoolDeposit.sol";
import "../RocketBase.sol";
import {RocketNodeStakingInterface} from "../../interface/node/RocketNodeStakingInterface.sol";

import "hardhat/console.sol";
import {RocketMegapoolFactoryInterface} from "../../interface/megapool/RocketMegapoolFactoryInterface.sol";
import "../../interface/node/RocketNodeStakingInterface.sol";
import "../../interface/megapool/RocketMegapoolFactoryInterface.sol";

/// @notice Accepts user deposits and mints rETH; handles assignment of deposited ETH to megapools
contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaultWithdrawerInterface {

// Constants
uint256 private constant milliToWei = 10**15;
uint256 private constant milliToWei = 10 ** 15;
bytes32 private constant queueKeyVariable = keccak256("minipools.available.variable");
bytes32 private constant expressQueueNamespace = keccak256("deposit.queue.express");
bytes32 private constant standardQueueNamespace = keccak256("deposit.queue.standard");
Expand Down Expand Up @@ -83,8 +81,8 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul
uint256 minipoolCapacity = rocketMinipoolQueue.getEffectiveCapacity();
uint256 balance = getBalance();
// Calculate and return
if (minipoolCapacity >= balance) { return 0; }
else { return balance - minipoolCapacity; }
if (minipoolCapacity >= balance) {return 0;}
else {return balance - minipoolCapacity;}
}

/// @dev Callback required to receive ETH withdrawal from the vault
Expand Down Expand Up @@ -113,8 +111,9 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul
// case where capacityNeeded fits in the deposit pool without looking at the queue
if (rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled()) {
RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue"));
require(capacityNeeded <= maxDepositPoolSize + rocketMinipoolQueue.getEffectiveCapacity(),
"The deposit pool size after depositing (and matching with minipools) exceeds the maximum size");
uint256 capacity = rocketMinipoolQueue.getEffectiveCapacity();
capacity += getUint("deposit.pool.requested.total");
require(capacityNeeded <= maxDepositPoolSize + capacity, "The deposit pool size after depositing exceeds the maximum size");
} else {
revert("The deposit pool size after depositing exceeds the maximum size");
}
Expand Down Expand Up @@ -142,7 +141,8 @@ 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 + rocketMinipoolQueue.getEffectiveCapacity();
maxCapacity += rocketMinipoolQueue.getEffectiveCapacity();
maxCapacity += getUint("deposit.pool.requested.total");
}
// Check we aren't already over
if (depositPoolBalance >= maxCapacity) {
Expand All @@ -168,7 +168,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul
// Withdraw ETH from the vault
rocketVault.withdrawEther(_amount);
// Send it to msg.sender (function modifier verifies msg.sender is RocketNodeDeposit)
(bool success, ) = address(msg.sender).call{value: _amount}("");
(bool success,) = address(msg.sender).call{value: _amount}("");
require(success, "Failed to send ETH");
}

Expand Down Expand Up @@ -223,6 +223,16 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul
return _assignDeposits(rocketDAOProtocolSettingsDeposit);
}

/// @dev If deposit assignments are enabled, assigns a single deposit
function maybeAssignOneDeposit() override external onlyThisLatestContract {
RocketDAOProtocolSettingsDepositInterface rocketDAOProtocolSettingsDeposit = RocketDAOProtocolSettingsDepositInterface(getContractAddress("rocketDAOProtocolSettingsDeposit"));
if (!rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled()) {
return;
}
// TODO: Clarify in RPIP "If possible, deposit SHALL assign one validator as described below" if this means node deposit should assign to legacy minipools too
assignMegapools(1);
}

/// @dev Assigns deposits to available minipools, returns false if assignment is currently disabled
function _assignDeposits(RocketDAOProtocolSettingsDepositInterface _rocketDAOProtocolSettingsDeposit) private returns (bool) {
// Check if assigning deposits is enabled
Expand All @@ -234,13 +244,27 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul
if (addressQueueStorage.getLength(queueKeyVariable) > 0) {
RocketMinipoolQueueInterface rocketMinipoolQueue = RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue"));
_assignDepositsLegacy(rocketMinipoolQueue, _rocketDAOProtocolSettingsDeposit);
return true;
} else {
// Then assign megapools
_assignMegapools(_rocketDAOProtocolSettingsDeposit);
}
// Assign megapools
assignMegapools(msg.value / 32 ether);
return true;
}

function _assignMegapools(RocketDAOProtocolSettingsDepositInterface _rocketDAOProtocolSettingsDeposit) private {
// Load contracts
RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool"));
// Calculate the number of minipools to assign
// TODO: Confirm whether we still want to support socialised assignments or whether the RPIP intends for them to be entirely removed (improve gas if removed)
uint256 maxAssignments = _rocketDAOProtocolSettingsDeposit.getMaximumDepositAssignments();
uint256 scalingCount = msg.value / 32 ether;
uint256 assignments = _rocketDAOProtocolSettingsDeposit.getMaximumDepositSocialisedAssignments() + scalingCount;
if (assignments > maxAssignments) {
assignments = maxAssignments;
}
assignMegapools(assignments);
}

/// @dev Assigns deposits using the legacy minipool queue
function _assignDepositsLegacy(RocketMinipoolQueueInterface _rocketMinipoolQueue, RocketDAOProtocolSettingsDepositInterface _rocketDAOProtocolSettingsDeposit) private {
// Load contracts
Expand All @@ -258,7 +282,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul
assignments = maxAssignments;
}
address[] memory minipools = _rocketMinipoolQueue.dequeueMinipools(assignments);
if (minipools.length > 0){
if (minipools.length > 0) {
// Withdraw ETH from vault
uint256 totalEther = minipools.length / variableDepositAmount;
rocketVault.withdrawEther(totalEther);
Expand Down Expand Up @@ -307,13 +331,15 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul
// Enqueue megapool
bytes32 namespace = getQueueNamespace(_expressQueue);
DepositQueueValue memory value = DepositQueueValue({
receiver: msg.sender, // Megapool address
receiver: msg.sender, // Megapool address
validatorId: uint32(_validatorId), // Incrementing id per validator in a megapool
suppliedValue: uint32(_bondAmount / milliToWei), // NO bond amount
requestedValue: uint32(_amount / milliToWei) // Amount being requested
});
LinkedListStorageInterface linkedListStorage = LinkedListStorageInterface(getContractAddress("linkedListStorage"));
linkedListStorage.enqueueItem(namespace, value);
// Increase requested balance
addUint("deposit.pool.requested.total", _amount);
}

function exitQueue(uint256 _validatorId, bool _expressQueue) external onlyRegisteredMegapool(msg.sender) {
Expand All @@ -330,6 +356,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul

/// @notice Assigns funds to megapools at the front of the queue if enough ETH is available
/// @param _count The maximum number of megapools to assign in this call
// TODO: Make this only callable internally or via RocketNodeDeposit
function assignMegapools(uint256 _count) override public {
if (_count == 0) {
// Nothing to do
Expand All @@ -346,13 +373,14 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul

// TODO: Parameterise express_queue_rate
uint256 expressQueueRate = 2;
uint256 totalSent = 0;

for (uint256 i = 0; i < _count; i++) {
if (expressQueueLength == 0 && standardQueueLength == 0) {
break;
}

bool express = queueIndex % (expressQueueRate+1) != 0;
bool express = queueIndex % (expressQueueRate + 1) != 0;

if (express && expressQueueLength == 0) {
express = false;
Expand All @@ -379,6 +407,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul

// Account for node balance
nodeBalanceUsed += head.suppliedValue;
totalSent += ethRequired;

// Update counts for next iteration
queueIndex ++;
Expand All @@ -392,6 +421,7 @@ contract RocketDepositPool is RocketBase, RocketDepositPoolInterface, RocketVaul
// Store state changes
subUint("deposit.pool.node.balance", nodeBalanceUsed);
setUint("megapool.queue.index", queueIndex);
subUint("deposit.pool.requested.total", totalSent);
}

/// @notice
Expand Down
44 changes: 22 additions & 22 deletions contracts/contract/node/RocketNodeDeposit.sol
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.18;

import {RocketStorageInterface} from "../../interface/RocketStorageInterface.sol";
import {RocketVaultInterface} from "../../interface/RocketVaultInterface.sol";
import {RocketDAOProtocolSettingsMinipoolInterface} from "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol";
import {RocketDAOProtocolSettingsNodeInterface} from "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol";
import {RocketDepositPoolInterface} from "../../interface/deposit/RocketDepositPoolInterface.sol";
import {RocketMegapoolFactoryInterface} from "../../interface/megapool/RocketMegapoolFactoryInterface.sol";
import {RocketMegapoolInterface} from "../../interface/megapool/RocketMegapoolInterface.sol";
import {RocketMinipoolInterface} from "../../interface/minipool/RocketMinipoolInterface.sol";
import {RocketMinipoolManagerInterface} from "../../interface/minipool/RocketMinipoolManagerInterface.sol";
import {RocketMinipoolQueueInterface} from "../../interface/minipool/RocketMinipoolQueueInterface.sol";
import {RocketNetworkFeesInterface} from "../../interface/network/RocketNetworkFeesInterface.sol";
import {RocketNetworkVotingInterface} from "../../interface/network/RocketNetworkVotingInterface.sol";
import {RocketNodeDepositInterface} from "../../interface/node/RocketNodeDepositInterface.sol";
import {RocketNodeManagerInterface} from "../../interface/node/RocketNodeManagerInterface.sol";
import {RocketNodeStakingInterface} from "../../interface/node/RocketNodeStakingInterface.sol";
import {RocketBase} from "../RocketBase.sol";
import {RocketNetworkSnapshots} from "../network/RocketNetworkSnapshots.sol";

/// @notice
import "../../interface/RocketStorageInterface.sol";
import "../../interface/RocketVaultInterface.sol";
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol";
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol";
import "../../interface/deposit/RocketDepositPoolInterface.sol";
import "../../interface/megapool/RocketMegapoolFactoryInterface.sol";
import "../../interface/megapool/RocketMegapoolInterface.sol";
import "../../interface/minipool/RocketMinipoolInterface.sol";
import "../../interface/minipool/RocketMinipoolManagerInterface.sol";
import "../../interface/minipool/RocketMinipoolQueueInterface.sol";
import "../../interface/network/RocketNetworkFeesInterface.sol";
import "../../interface/network/RocketNetworkVotingInterface.sol";
import "../../interface/network/RocketNetworkSnapshotsInterface.sol";
import "../../interface/node/RocketNodeDepositInterface.sol";
import "../../interface/node/RocketNodeManagerInterface.sol";
import "../../interface/node/RocketNodeStakingInterface.sol";
import "../RocketBase.sol";

/// @notice Entry point for node operators to perform deposits for the creation of new validators on the network
contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface {
// Constants
uint256 constant internal pubKeyLength = 48;
Expand Down Expand Up @@ -207,7 +207,7 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface {
RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool"));
rocketDepositPool.nodeDeposit{value: msg.value}(_bondAmount);
// Attempt to assign 1 megapool
rocketDepositPool.assignMegapools(1);
rocketDepositPool.maybeAssignOneDeposit();
}

/// @notice Called by minipools during bond reduction to increase the amount of ETH the node operator has
Expand All @@ -223,7 +223,7 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface {
function _increaseEthMatched(address _nodeAddress, uint256 _amount) private {
// Check amount doesn't exceed limits
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots"));
uint256 ethMatched = rocketNodeStaking.getNodeETHMatched(_nodeAddress) + _amount;
require(
ethMatched <= rocketNodeStaking.getNodeETHMatchedLimit(_nodeAddress),
Expand All @@ -237,7 +237,7 @@ contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface {
/// @dev Increases the amount of ETH supplied by a node operator as bond
function _increaseEthProvided(address _nodeAddress, uint256 _amount) private {
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots"));
uint256 ethProvided = rocketNodeStaking.getNodeETHProvided(_nodeAddress) + _amount;
bytes32 key = keccak256(abi.encodePacked("eth.provided.node.amount", _nodeAddress));
rocketNetworkSnapshots.push(key, uint224(ethProvided));
Expand Down
Loading

0 comments on commit 7ab961c

Please sign in to comment.