Skip to content

Commit 1367c19

Browse files
committed
Account for change in minipool staking counts during bond reduction
1 parent 680144d commit 1367c19

5 files changed

Lines changed: 81 additions & 9 deletions

File tree

contracts/contract/minipool/RocketMinipoolBondReducer.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import "../../interface/dao/node/settings/RocketDAONodeTrustedSettingsMinipoolIn
1212
import "../../interface/dao/node/RocketDAONodeTrustedInterface.sol";
1313
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsRewardsInterface.sol";
1414
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol";
15+
import "../../interface/minipool/RocketMinipoolManagerInterface.sol";
1516

1617
/// @notice Handles bond reduction window and trusted node cancellation
1718
contract RocketMinipoolBondReducer is RocketBase, RocketMinipoolBondReducerInterface {

contracts/contract/minipool/RocketMinipoolDelegate.sol

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -646,20 +646,25 @@ contract RocketMinipoolDelegate is RocketMinipoolStorageLayout, RocketMinipoolIn
646646
// Approve reduction and handle external state changes
647647
RocketMinipoolBondReducerInterface rocketBondReducer = RocketMinipoolBondReducerInterface(getContractAddress("rocketMinipoolBondReducer"));
648648
uint256 previousBond = nodeDepositBalance;
649-
uint256 newBondAmount = rocketBondReducer.reduceBondAmount();
649+
uint256 newBond = rocketBondReducer.reduceBondAmount();
650650
// Update user/node balances
651-
userDepositBalance = getUserDepositBalance().add(previousBond.sub(newBondAmount));
652-
nodeDepositBalance = newBondAmount;
651+
userDepositBalance = getUserDepositBalance().add(previousBond.sub(newBond));
652+
nodeDepositBalance = newBond;
653653
// Reset node fee to current network rate
654654
RocketNetworkFeesInterface rocketNetworkFees = RocketNetworkFeesInterface(getContractAddress("rocketNetworkFees"));
655-
nodeFee = rocketNetworkFees.getNodeFee();
655+
uint256 prevFee = nodeFee;
656+
uint256 newFee = rocketNetworkFees.getNodeFee();
657+
nodeFee = newFee;
658+
// Update staking minipool counts and fee numerator
659+
RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager"));
660+
rocketMinipoolManager.updateNodeStakingMinipoolCount(previousBond, newBond, prevFee, newFee);
656661
// Break state to prevent rollback exploit
657662
if (depositType != MinipoolDeposit.Variable) {
658-
userDepositBalanceLegacy = 2**256-1;
663+
userDepositBalanceLegacy = 2 ** 256 - 1;
659664
depositType = MinipoolDeposit.Variable;
660665
}
661666
// Emit event
662-
emit BondReduced(previousBond, newBondAmount, block.timestamp);
667+
emit BondReduced(previousBond, newBond, block.timestamp);
663668
}
664669

665670
/// @dev Distributes the current contract balance based on capital ratio and node fee

contracts/contract/minipool/RocketMinipoolManager.sol

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,42 @@ contract RocketMinipoolManager is RocketBase, RocketMinipoolManagerInterface {
251251
return abi.encodePacked(byte(0x01), bytes11(0x0), address(_minipoolAddress));
252252
}
253253

254+
/// @notice Decrements a node operator's number of staking minipools based on the minipools prior bond amount and
255+
/// increments it based on their new bond amount.
256+
/// @param _previousBond The minipool's previous bond value
257+
/// @param _newBond The minipool's new bond value
258+
/// @param _previousFee The fee of the minipool prior to the bond change
259+
/// @param _newFee The fee of the minipool after the bond change
260+
function updateNodeStakingMinipoolCount(uint256 _previousBond, uint256 _newBond, uint256 _previousFee, uint256 _newFee) override external onlyLatestContract("rocketMinipoolManager", address(this)) onlyRegisteredMinipool(msg.sender) {
261+
bytes32 nodeKey;
262+
bytes32 numeratorKey;
263+
// Get contracts
264+
RocketMinipoolInterface minipool = RocketMinipoolInterface(msg.sender);
265+
address nodeAddress = minipool.getNodeAddress();
266+
// Try to distribute current fees at previous average commission rate
267+
_tryDistribute(nodeAddress);
268+
// Decrement previous bond count
269+
if (_previousBond == 16 ether){
270+
nodeKey = keccak256(abi.encodePacked("node.minipools.staking.count", nodeAddress));
271+
numeratorKey = keccak256(abi.encodePacked("node.average.fee.numerator", nodeAddress));
272+
} else {
273+
nodeKey = keccak256(abi.encodePacked("node.minipools.staking.count", nodeAddress, _previousBond));
274+
numeratorKey = keccak256(abi.encodePacked("node.average.fee.numerator", nodeAddress, _previousBond));
275+
}
276+
subUint(nodeKey, 1);
277+
subUint(numeratorKey, _previousFee);
278+
// Increment new bond count
279+
if (_newBond == 16 ether){
280+
nodeKey = keccak256(abi.encodePacked("node.minipools.staking.count", nodeAddress));
281+
numeratorKey = keccak256(abi.encodePacked("node.average.fee.numerator", nodeAddress));
282+
} else {
283+
nodeKey = keccak256(abi.encodePacked("node.minipools.staking.count", nodeAddress, _newBond));
284+
numeratorKey = keccak256(abi.encodePacked("node.average.fee.numerator", nodeAddress, _newBond));
285+
}
286+
addUint(nodeKey, 1);
287+
addUint(numeratorKey, _newFee);
288+
}
289+
254290
/// @dev Increments a node operator's number of staking minipools and calculates updated average node fee.
255291
/// Must be called from the minipool itself as msg.sender is used to query the minipool's node fee
256292
/// @param _nodeAddress The node address to increment the number of staking minipools of

contracts/interface/minipool/RocketMinipoolManagerInterface.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ interface RocketMinipoolManagerInterface {
2828
function getMinipoolExists(address _minipoolAddress) external view returns (bool);
2929
function getMinipoolDestroyed(address _minipoolAddress) external view returns (bool);
3030
function getMinipoolPubkey(address _minipoolAddress) external view returns (bytes memory);
31+
function updateNodeStakingMinipoolCount(uint256 _previousBond, uint256 _newBond, uint256 _previousFee, uint256 _newFee) external;
3132
function getMinipoolWithdrawalCredentials(address _minipoolAddress) external pure returns (bytes memory);
3233
function createMinipool(address _nodeAddress, uint256 _salt) external returns (RocketMinipoolInterface);
3334
function createVacantMinipool(address _nodeAddress, uint256 _salt, bytes calldata _validatorPubkey, uint256 _bondAmount, uint256 _currentBalance) external returns (RocketMinipoolInterface);

test/minipool/scenario-reduce-bond.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { RocketMinipoolBondReducer, RocketNodeDeposit, RocketNodeStaking } from '../_utils/artifacts';
1+
import {
2+
RocketMinipoolBondReducer,
3+
RocketMinipoolManager,
4+
RocketNodeDeposit,
5+
RocketNodeStaking,
6+
} from '../_utils/artifacts';
27
import { assertBN } from '../_helpers/bn';
38

49

@@ -7,8 +12,12 @@ export async function reduceBond(minipool, txOptions = null) {
712
const rocketNodeDeposit = await RocketNodeDeposit.deployed();
813
const rocketNodeStaking = await RocketNodeStaking.deployed();
914
const rocketMinipoolBondReducer = await RocketMinipoolBondReducer.deployed();
15+
const rocketNodeManager = await RocketMinipoolManager.deployed();
1016
const node = await minipool.getNodeAddress();
1117

18+
const newBond = await rocketMinipoolBondReducer.getReduceBondValue(minipool.address);
19+
const prevBond = await minipool.getNodeDepositBalance();
20+
1221
// Get minipool balances
1322
function getMinipoolBalances() {
1423
return Promise.all([
@@ -22,19 +31,39 @@ export async function reduceBond(minipool, txOptions = null) {
2231
);
2332
}
2433

34+
// Get node details
35+
function getNodeDetails() {
36+
return Promise.all([
37+
rocketNodeManager.getNodeStakingMinipoolCountBySize(node, prevBond),
38+
rocketNodeManager.getNodeStakingMinipoolCountBySize(node, newBond),
39+
rocketNodeManager.getNodeStakingMinipoolCount(node),
40+
]).then(
41+
([prevBondCount, newBondCount, totalCount]) =>
42+
({prevBondCount, newBondCount, totalCount})
43+
)
44+
}
45+
2546
// Get new bond amount
2647
const amount = await rocketMinipoolBondReducer.getReduceBondValue(minipool.address);
2748

2849
// Record balances before and after calling reduce bond function
2950
const balances1 = await getMinipoolBalances();
51+
const details1 = await getNodeDetails();
3052
await minipool.reduceBondAmount(txOptions);
3153
const balances2 = await getMinipoolBalances();
32-
33-
const delta = balances1.nodeDepositBalance.sub(amount);
54+
const details2 = await getNodeDetails();
3455

3556
// Verify results
57+
const delta = balances1.nodeDepositBalance.sub(amount);
3658
assertBN.equal(balances2.nodeDepositBalance, delta);
3759
assertBN.equal(balances2.userDepositBalance.sub(balances1.userDepositBalance), delta);
3860
assertBN.equal(balances2.nodeDepositCredit.sub(balances1.nodeDepositCredit), delta);
3961
assertBN.equal(balances2.ethMatched.sub(balances1.ethMatched), delta);
62+
63+
// Overall number of minipools shouldn't change
64+
assertBN.equal(details2.totalCount, details1.totalCount);
65+
// Prev bond amount should decrement by 1
66+
assertBN.equal(details1.prevBondCount.sub(details2.prevBondCount), '1'.BN);
67+
// New bond amount should increment by 1
68+
assertBN.equal(details2.newBondCount.sub(details1.newBondCount), '1'.BN);
4069
}

0 commit comments

Comments
 (0)