Skip to content

Commit

Permalink
Fix fallback calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
kanewallmann committed Apr 3, 2024
1 parent 786ebfb commit f4e2235
Show file tree
Hide file tree
Showing 6 changed files with 504 additions and 68 deletions.
56 changes: 29 additions & 27 deletions contracts/contract/minipool/RocketMinipoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import "../../interface/network/RocketNetworkPenaltiesInterface.sol";
import "../../interface/minipool/RocketMinipoolPenaltyInterface.sol";
import "../../interface/node/RocketNodeDepositInterface.sol";
import "../network/RocketNetworkSnapshots.sol";
import "../node/RocketNodeStaking.sol";

/// @notice Minipool creation, removal and management
contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
Expand Down Expand Up @@ -154,9 +155,17 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
/// @notice Get the number of minipools owned by a node that are not finalised
/// @param _nodeAddress The node operator to query the count of active minipools of
function getNodeActiveMinipoolCount(address _nodeAddress) override public view returns (uint256) {
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
bytes32 key = keccak256(abi.encodePacked("minipools.active.count", _nodeAddress));
return uint256(rocketNetworkSnapshots.latestValue(key));
RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots"));
(bool exists,, uint224 count) = rocketNetworkSnapshots.latest(key);
if (!exists){
// Fallback to old value
AddressSetStorageInterface addressSetStorage = AddressSetStorageInterface(getContractAddress("addressSetStorage"));
uint256 finalised = getUint(keccak256(abi.encodePacked("node.minipools.finalised.count", _nodeAddress)));
uint256 total = addressSetStorage.getCount(keccak256(abi.encodePacked("node.minipools.index", _nodeAddress)));
return total - finalised;
}
return uint256(count);
}

/// @notice Get the number of minipools owned by a node that are finalised
Expand Down Expand Up @@ -365,6 +374,8 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
/// @dev Increments a node operator's number of minipools that have been finalised
/// @param _nodeAddress The node operator to increment finalised minipool count for
function incrementNodeFinalisedMinipoolCount(address _nodeAddress) override external onlyLatestContract("rocketMinipoolManager", address(this)) onlyRegisteredMinipool(msg.sender) {
// Get active minipool count (before increasing finalised count in case of fallback calculation)
uint256 activeMinipoolCount = getNodeActiveMinipoolCount(_nodeAddress);
// Can only finalise a minipool once
bytes32 finalisedKey = keccak256(abi.encodePacked("node.minipools.finalised", msg.sender));
require(!getBool(finalisedKey), "Minipool has already been finalised");
Expand All @@ -374,20 +385,15 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
// Update the total count
addUint(keccak256(bytes("minipools.finalised.count")), 1);
// Update ETH matched
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
uint256 ethMatched = rocketNodeStaking.getNodeETHMatched(_nodeAddress);
ethMatched -= RocketMinipoolInterface(msg.sender).getUserDepositBalance();
bytes32 key = keccak256(abi.encodePacked("eth.matched.node.amount", _nodeAddress));
uint256 ethMatched = rocketNetworkSnapshots.latestValue(key);
if (ethMatched == 0) {
ethMatched = getNodeActiveMinipoolCount(_nodeAddress) * 16 ether;
} else {
RocketMinipoolInterface minipool = RocketMinipoolInterface(msg.sender);
ethMatched = ethMatched - minipool.getUserDepositBalance();
}
rocketNetworkSnapshots.push(key, uint32(block.number), uint224(ethMatched));
// Decrement active count
key = keccak256(abi.encodePacked("minipools.active.count", _nodeAddress));
uint224 count = rocketNetworkSnapshots.latestValue(key);
rocketNetworkSnapshots.push(key, uint32(block.number), count - 1);
rocketNetworkSnapshots.push(key, uint32(block.number), uint224(activeMinipoolCount - 1));
}

/// @dev Create a minipool. Only accepts calls from the RocketNodeDeposit contract
Expand All @@ -400,9 +406,11 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
{ // Local scope to prevent stack too deep error
RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool"));
// Check global minipool limit
uint256 totalMinipoolCount = getActiveMinipoolCount();
require(totalMinipoolCount + 1 <= rocketDAOProtocolSettingsMinipool.getMaximumCount(), "Global minipool limit reached");
uint256 totalActiveMinipoolCount = getActiveMinipoolCount();
require(totalActiveMinipoolCount + 1 <= rocketDAOProtocolSettingsMinipool.getMaximumCount(), "Global minipool limit reached");
}
// Get current active minipool count for this node operator (before we insert into address set in case it uses fallback calc)
uint256 activeMinipoolCount = getNodeActiveMinipoolCount(_nodeAddress);
// Create minipool contract
address contractAddress = deployContract(_nodeAddress, _salt);
// Initialise minipool data
Expand All @@ -413,8 +421,7 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
// Increment active count
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
bytes32 key = keccak256(abi.encodePacked("minipools.active.count", _nodeAddress));
uint224 count = rocketNetworkSnapshots.latestValue(key);
rocketNetworkSnapshots.push(key, uint32(block.number), count + 1);
rocketNetworkSnapshots.push(key, uint32(block.number), uint224(activeMinipoolCount + 1));
// Emit minipool created event
emit MinipoolCreated(contractAddress, _nodeAddress, block.timestamp);
// Return created minipool address
Expand Down Expand Up @@ -473,23 +480,19 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
RocketMinipoolInterface minipool = RocketMinipoolInterface(msg.sender);
address nodeAddress = minipool.getNodeAddress();
// Update ETH matched
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));
uint256 ethMatched = rocketNodeStaking.getNodeETHMatched(nodeAddress);
ethMatched = ethMatched - minipool.getUserDepositBalance();
// Record in snapshot manager
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
bytes32 key = keccak256(abi.encodePacked("eth.matched.node.amount", nodeAddress));
uint256 ethMatched = rocketNetworkSnapshots.latestValue(key);
if (ethMatched == 0) {
ethMatched = getNodeActiveMinipoolCount(nodeAddress) * 16 ether;
}
// Handle legacy minipools
if (minipool.getDepositType() == MinipoolDeposit.Variable) {
ethMatched = ethMatched - minipool.getUserDepositBalance();
} else {
ethMatched = ethMatched - 16 ether;
}
rocketNetworkSnapshots.push(key, uint32(block.number), uint224(ethMatched));
// Update minipool data
setBool(keccak256(abi.encodePacked("minipool.exists", msg.sender)), false);
// Record minipool as destroyed to prevent recreation at same address
setBool(keccak256(abi.encodePacked("minipool.destroyed", msg.sender)), true);
// Get number of active minipools (before removing from address set in case of fallback calculation)
uint256 activeMinipoolCount = getNodeActiveMinipoolCount(nodeAddress);
// Remove minipool from indexes
addressSetStorage.removeItem(keccak256(abi.encodePacked("minipools.index")), msg.sender);
addressSetStorage.removeItem(keccak256(abi.encodePacked("node.minipools.index", nodeAddress)), msg.sender);
Expand All @@ -499,8 +502,7 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
deleteAddress(keccak256(abi.encodePacked("validator.minipool", pubkey)));
// Decrement active count
key = keccak256(abi.encodePacked("minipools.active.count", nodeAddress));
uint224 count = rocketNetworkSnapshots.latestValue(key);
rocketNetworkSnapshots.push(key, uint32(block.number), count - 1);
rocketNetworkSnapshots.push(key, uint32(block.number), uint224(activeMinipoolCount - 1));
// Emit minipool destroyed event
emit MinipoolDestroyed(msg.sender, nodeAddress, block.timestamp);
}
Expand Down
72 changes: 34 additions & 38 deletions contracts/contract/node/RocketNodeStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
function getNodeRPLStake(address _nodeAddress) override public view returns (uint256) {
bytes32 key = keccak256(abi.encodePacked("rpl.staked.node.amount", _nodeAddress));
RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots"));
uint256 stake = uint256(rocketNetworkSnapshots.latestValue(key));
if (stake == 0){
(bool exists,, uint224 value) = rocketNetworkSnapshots.latest(key);
uint256 stake = uint256(value);
if (!exists){
// Fallback to old value
stake = getUint(key);
}
Expand All @@ -84,10 +85,10 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
/// @dev Increases a node operator's RPL stake
/// @param _amount How much to increase by
function increaseNodeRPLStake(address _nodeAddress, uint256 _amount) private {
bytes32 key = keccak256(abi.encodePacked("rpl.staked.node.amount", _nodeAddress));
RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots"));
uint224 value = rocketNetworkSnapshots.latestValue(key);
if (value == 0){
bytes32 key = keccak256(abi.encodePacked("rpl.staked.node.amount", _nodeAddress));
(bool exists,, uint224 value) = rocketNetworkSnapshots.latest(key);
if (!exists){
value = uint224(getUint(key));
}
rocketNetworkSnapshots.push(key, uint32(block.number), value + uint224(_amount));
Expand All @@ -96,10 +97,10 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
/// @dev Decrease a node operator's RPL stake
/// @param _amount How much to decrease by
function decreaseNodeRPLStake(address _nodeAddress, uint256 _amount) private {
bytes32 key = keccak256(abi.encodePacked("rpl.staked.node.amount", _nodeAddress));
RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots"));
uint224 value = rocketNetworkSnapshots.latestValue(key);
if (value == 0){
bytes32 key = keccak256(abi.encodePacked("rpl.staked.node.amount", _nodeAddress));
(bool exists,, uint224 value) = rocketNetworkSnapshots.latest(key);
if (!exists){
value = uint224(getUint(key));
}
rocketNetworkSnapshots.push(key, uint32(block.number), value - uint224(_amount));
Expand All @@ -110,18 +111,18 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
function getNodeETHMatched(address _nodeAddress) override public view returns (uint256) {
RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots"));
bytes32 key = keccak256(abi.encodePacked("eth.matched.node.amount", _nodeAddress));
uint256 ethMatched = uint256(rocketNetworkSnapshots.latestValue(key));

if (ethMatched > 0) {
return ethMatched;
(bool exists, , uint224 value) = rocketNetworkSnapshots.latest(key);
if (exists) {
// Value was previously set in a snapshot so return that
return value;
} else {
// Fallback to old method
ethMatched = getUint(key);

uint256 ethMatched = getUint(key);
if (ethMatched > 0) {
// Value was previously calculated and stored so return that
return ethMatched;
} else {
// Fallback for backwards compatibility before ETH matched was recorded (all minipools matched 16 ETH from protocol)
// Fallback for backwards compatibility before ETH matched was recorded (all legacy minipools matched 16 ETH from protocol)
RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager"));
return rocketMinipoolManager.getNodeActiveMinipoolCount(_nodeAddress) * 16 ether;
}
Expand All @@ -135,9 +136,7 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager"));
uint256 activeMinipoolCount = rocketMinipoolManager.getNodeActiveMinipoolCount(_nodeAddress);
// Retrieve stored ETH matched value
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
bytes32 key = keccak256(abi.encodePacked("eth.matched.node.amount", _nodeAddress));
uint256 ethMatched = uint256(rocketNetworkSnapshots.latestValue(key));
uint256 ethMatched = getNodeETHMatched(_nodeAddress);
if (ethMatched > 0) {
RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool"));
uint256 launchAmount = rocketDAOProtocolSettingsMinipool.getLaunchBalance();
Expand All @@ -154,10 +153,7 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
/// The value is a 1e18 precision fixed point integer value of (node capital + user capital) / node capital.
/// @param _nodeAddress The address of the node operator to query
function getNodeETHCollateralisationRatio(address _nodeAddress) override public view returns (uint256) {
RocketNetworkSnapshots rocketNetworkSnapshots = RocketNetworkSnapshots(getContractAddress("rocketNetworkSnapshots"));
bytes32 key = keccak256(abi.encodePacked("eth.matched.node.amount", _nodeAddress));
uint256 ethMatched = uint256(rocketNetworkSnapshots.latestValue(key));

uint256 ethMatched = getNodeETHMatched(_nodeAddress);
if (ethMatched == 0) {
// Node operator only has legacy minipools and all legacy minipools had a 1:1 ratio
return calcBase * 2;
Expand Down Expand Up @@ -264,22 +260,22 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
/// @param _nodeAddress The address of the node operator to stake on behalf of
/// @param _amount The amount of RPL to stake
function stakeRPLFor(address _nodeAddress, uint256 _amount) override public onlyLatestContract("rocketNodeStaking", address(this)) onlyRegisteredNode(_nodeAddress) {
// Must be node's RPL withdrawal address if set or the node's address or an allow listed address or rocketMerkleDistributorMainnet
if (msg.sender != getAddress(keccak256(abi.encodePacked("contract.address", "rocketMerkleDistributorMainnet")))) {
RocketNodeManagerInterface rocketNodeManager = RocketNodeManagerInterface(getContractAddress("rocketNodeManager"));
bool fromNode = false;
if (rocketNodeManager.getNodeRPLWithdrawalAddressIsSet(_nodeAddress)) {
address rplWithdrawalAddress = rocketNodeManager.getNodeRPLWithdrawalAddress(_nodeAddress);
fromNode = msg.sender == rplWithdrawalAddress;
} else {
address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(_nodeAddress);
fromNode = (msg.sender == _nodeAddress) || (msg.sender == withdrawalAddress);
}
if (!fromNode) {
require(getBool(keccak256(abi.encodePacked("node.stake.for.allowed", _nodeAddress, msg.sender))), "Not allowed to stake for");
}
}
_stakeRPL(_nodeAddress, _amount);
// Must be node's RPL withdrawal address if set or the node's address or an allow listed address or rocketMerkleDistributorMainnet
if (msg.sender != getAddress(keccak256(abi.encodePacked("contract.address", "rocketMerkleDistributorMainnet")))) {
RocketNodeManagerInterface rocketNodeManager = RocketNodeManagerInterface(getContractAddress("rocketNodeManager"));
bool fromNode = false;
if (rocketNodeManager.getNodeRPLWithdrawalAddressIsSet(_nodeAddress)) {
address rplWithdrawalAddress = rocketNodeManager.getNodeRPLWithdrawalAddress(_nodeAddress);
fromNode = msg.sender == rplWithdrawalAddress;
} else {
address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(_nodeAddress);
fromNode = (msg.sender == _nodeAddress) || (msg.sender == withdrawalAddress);
}
if (!fromNode) {
require(getBool(keccak256(abi.encodePacked("node.stake.for.allowed", _nodeAddress, msg.sender))), "Not allowed to stake for");
}
}
_stakeRPL(_nodeAddress, _amount);
}

/// @notice Sets the allow state for this node to perform functions that require locking RPL
Expand Down
7 changes: 6 additions & 1 deletion test/dao/dao-protocol-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
RocketDAOProtocolSettingsMinipool,
RocketDAOProtocolSettingsNetwork,
RocketDAOProtocolSettingsProposals,
RocketDAOProtocolSettingsRewards, RocketDAOProtocolSettingsRewardsNew,
RocketDAOProtocolSettingsRewards,
RocketDAOProtocolSettingsRewardsNew,
RocketMinipoolManagerNew,
RocketNodeManagerNew,
RocketNodeStakingNew,
} from '../_utils/artifacts';
import {
cloneLeaves,
Expand Down Expand Up @@ -540,6 +544,7 @@ export default function() {
// Setup
await setDAOProtocolBootstrapEnableGovernance();
await mockNodeSet();

await createNode(1, proposer);

// Give the proposer 150% collateral + proposal bond + 50
Expand Down
6 changes: 4 additions & 2 deletions test/minipool/scenario-close.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { RocketNodeManager, RocketNodeStaking } from '../_utils/artifacts';
import { RocketNodeManager, RocketNodeStaking, RocketNodeStakingNew } from '../_utils/artifacts';
import { assertBN } from '../_helpers/bn';
import { upgradeExecuted } from '../_utils/upgrade';


// Close a minipool
export async function close(minipool, txOptions) {
// Load contracts
const upgraded = await upgradeExecuted();
const rocketNodeManager = await RocketNodeManager.deployed();
const rocketNodeStaking = await RocketNodeStaking.deployed();
const rocketNodeStaking = upgraded ? await RocketNodeStakingNew.deployed() : await RocketNodeStaking.deployed();

// Get parameters
let nodeAddress = await minipool.getNodeAddress.call();
Expand Down
2 changes: 2 additions & 0 deletions test/rocket-pool-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { injectBNHelpers } from './_helpers/bn';
import { checkInvariants } from './_helpers/invariants';
import networkSnapshotsTests from './network/network-snapshots-tests';
import networkVotingTests from './network/network-voting-tests';
import upgradeTests from './upgrade/upgrade-tests';

// Header
console.log('\n');
Expand Down Expand Up @@ -69,6 +70,7 @@ auctionTests();
depositPoolTests();
minipoolScrubTests();
minipoolTests();
upgradeTests();
minipoolVacantTests();
minipoolStatusTests();
minipoolWithdrawalTests();
Expand Down
Loading

0 comments on commit f4e2235

Please sign in to comment.