Skip to content

Commit afba69a

Browse files
committed
Remove minimum RPL requirement check during RPL slash event
1 parent 75418d2 commit afba69a

2 files changed

Lines changed: 101 additions & 19 deletions

File tree

contracts/contract/node/RocketNodeStaking.sol

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
290290
// Withdraw any RPL that has been unstaking long enough
291291
_withdrawUnstakingRPL(_nodeAddress);
292292
// Move RPL from staking to unstaking
293-
_decreaseNodeLegacyRPLStake(_nodeAddress, _amount);
293+
_decreaseNodeLegacyRPLStake(_nodeAddress, _amount, true);
294294
addUint(keccak256(abi.encodePacked("rpl.megapool.unstaking.amount", _nodeAddress)), _amount);
295295
// Reset the unstake time
296296
_setNodeLastUnstakeTime(_nodeAddress);
@@ -375,7 +375,7 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
375375
// Transfer slashed amount to auction contract
376376
if (rplSlashAmount > 0) rocketVault.transferToken("rocketAuctionManager", IERC20(getContractAddress("rocketTokenRPL")), rplSlashAmount);
377377
// Update RPL stake amounts
378-
_decreaseNodeLegacyRPLStake(_nodeAddress, rplSlashAmount);
378+
_decreaseNodeLegacyRPLStake(_nodeAddress, rplSlashAmount, false);
379379
// Mark minipool as slashed
380380
setBool(keccak256(abi.encodePacked("minipool.rpl.slashed", msg.sender)), true);
381381
// Emit RPL slashed event
@@ -447,26 +447,29 @@ contract RocketNodeStaking is RocketBase, RocketNodeStakingInterface {
447447
/// @dev Decreases a node operator's legacy staked RPL amount
448448
/// @param _nodeAddress Address of node to decrease legacy staked RPL for
449449
/// @param _amount Amount to decrease by
450-
function _decreaseNodeLegacyRPLStake(address _nodeAddress, uint256 _amount) internal {
450+
/// @param _ensureMinimums Whether to assert the NO has the minimum required legacy RPL stake and sufficient unlocked RPL for the decrease
451+
function _decreaseNodeLegacyRPLStake(address _nodeAddress, uint256 _amount, bool _ensureMinimums) internal {
451452
RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots"));
452453
bytes32 key = keccak256(abi.encodePacked("rpl.staked.node.amount", _nodeAddress));
453454
(,, uint224 totalStakedRPL) = rocketNetworkSnapshots.latest(key);
454455
_migrateLegacy(_nodeAddress, uint256(totalStakedRPL));
455-
// Check amount does not exceed amount staked
456+
// Compute legacy staked RPL storage key
456457
bytes32 legacyKey = keccak256(abi.encodePacked("rpl.legacy.staked.node.amount", _nodeAddress));
457-
uint256 legacyStakedRPL = getUint(legacyKey);
458-
// Check amount after decrease does not fall below minimum required
459-
uint256 minimumLegacyStakedRPL = getNodeMinimumLegacyRPLStake(_nodeAddress);
460-
require(
461-
legacyStakedRPL >= _amount + minimumLegacyStakedRPL,
462-
"Insufficient legacy staked RPL"
463-
);
464-
uint256 lockedRPL = getNodeLockedRPL(_nodeAddress);
465-
// Check node has enough unlocked RPL for the reduction
466-
require(
467-
uint256(totalStakedRPL) >= _amount + lockedRPL,
468-
"Insufficient RPL stake to reduce"
469-
);
458+
if (_ensureMinimums) {
459+
// Check amount after decrease does not fall below minimum required
460+
uint256 legacyStakedRPL = getUint(legacyKey);
461+
uint256 minimumLegacyStakedRPL = getNodeMinimumLegacyRPLStake(_nodeAddress);
462+
require(
463+
legacyStakedRPL >= _amount + minimumLegacyStakedRPL,
464+
"Insufficient legacy staked RPL"
465+
);
466+
// Check node has enough unlocked RPL for the reduction
467+
uint256 lockedRPL = getNodeLockedRPL(_nodeAddress);
468+
require(
469+
uint256(totalStakedRPL) >= _amount + lockedRPL,
470+
"Insufficient RPL stake to reduce"
471+
);
472+
}
470473
// Store new values
471474
rocketNetworkSnapshots.push(key, totalStakedRPL - uint224(_amount));
472475
subUint(legacyKey, _amount);

test-upgrade/tests/minipool-tests.js

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
RocketMinipoolPenalty,
1111
RocketNetworkPenalties,
1212
RocketNodeDeposit,
13-
RocketNodeManager,
13+
RocketNodeManager, RocketNodeStaking,
1414
} from '../../test/_utils/artifacts';
1515
import { shouldRevert } from '../../test/_utils/testing';
1616
import { createVacantMinipool, minipoolStates, promoteMinipool, stakeMinipool } from '../../test/_helpers/minipool';
@@ -21,6 +21,8 @@ import { getValidatorPubkey } from '../../test/_utils/beacon';
2121
import { deployMegapool } from '../../test/_helpers/megapool';
2222
import { beginUserDistribute, withdrawValidatorBalance } from '../../test/minipool/scenario-withdraw-validator-balance';
2323
import { setDAOProtocolBootstrapSetting } from '../../test/dao/scenario-dao-protocol-bootstrap';
24+
import { close } from '../../test/minipool/scenario-close';
25+
import { unstakeLegacyRpl } from '../../test/node/scenario-unstake-legacy-rpl';
2426
const { assertBN } = require('../../test/_helpers/bn');
2527
const { beforeEach, describe, before, it } = require('mocha');
2628
const { globalSnapShot } = require('../../test/_utils/snapshotting');
@@ -236,7 +238,7 @@ export default function() {
236238
await setNodeTrusted(trustedNode3, 'saas_3', 'node@home.com', owner);
237239
// Set max penalty rate
238240
let rocketMinipoolPenalty = await RocketMinipoolPenalty.deployed();
239-
rocketMinipoolPenalty.setMaxPenaltyRate('1'.ether, { from: owner });
241+
await rocketMinipoolPenalty.connect(owner).setMaxPenaltyRate('1'.ether);
240242
})
241243

242244
it(printTitle('node', 'can not bond reduce after upgrade'), async () => {
@@ -265,6 +267,24 @@ export default function() {
265267
await withdrawAndCheck(minipool, '36', node, false, '17.8', '18.2');
266268
});
267269

270+
it(printTitle('node', 'can exit a staking minipool and get slashed below minimum RPL requirement'), async () => {
271+
const rocketNodeStaking = await RocketNodeStaking.deployed()
272+
const stakedRPLBefore = await rocketNodeStaking.getNodeLegacyStakedRPL(node.address)
273+
await withdrawAndCheck(minipool, '15', node, false, '15', '0');
274+
const stakedRPLAfter = await rocketNodeStaking.getNodeLegacyStakedRPL(node.address)
275+
// RPL price is 0.01 ETH so should slash 100 RPL
276+
assertBN.equal(stakedRPLBefore - stakedRPLAfter, '100'.ether);
277+
});
278+
279+
it(printTitle('node', 'can be slashed greater than legacy staked RPL balance'), async () => {
280+
const rocketNodeStaking = await RocketNodeStaking.deployed()
281+
// Return 10 ETH which results int 600 RPL slashing
282+
await withdrawAndCheck(minipool, '10', node, false, '10', '0');
283+
const stakedRPLAfter = await rocketNodeStaking.getNodeLegacyStakedRPL(node.address)
284+
// RPL price is 0.01 ETH so should slash 100 RPL
285+
assertBN.equal(stakedRPLAfter, 0n);
286+
});
287+
268288
it(printTitle('trusted node', 'can submit minipool penalties'), async() => {
269289
let minipoolAddress = minipool.target;
270290
const rocketNetworkPenalties = await RocketNetworkPenalties.deployed()
@@ -300,5 +320,64 @@ export default function() {
300320
})
301321

302322
})
323+
324+
describe('With multiple staking legacy minipool', () => {
325+
let minipool1,
326+
minipool2,
327+
minipool3
328+
329+
beforeEach(async () => {
330+
// Register node and create a 16 ETH minipool
331+
await registerNode({ from: node });
332+
const minipoolRplStake = await getMinipoolMinimumRPLStake();
333+
await mintRPL(owner, node, minipoolRplStake * 3n);
334+
await stakeRPL(node, minipoolRplStake * 3n);
335+
minipool1 = (await createMinipool({ from: node, value: '16'.ether })).connect(node);
336+
minipool2 = (await createMinipool({ from: node, value: '16'.ether })).connect(node);
337+
minipool3 = (await createMinipool({ from: node, value: '16'.ether })).connect(node);
338+
// Execute upgrade
339+
await executeUpgrade(owner, upgradeContract, rocketStorageAddress);
340+
// Perform 3 user deposit that will assign all 3 minipools
341+
await userDeposit({ from: random, value: '32'.ether });
342+
await userDeposit({ from: random, value: '32'.ether });
343+
await userDeposit({ from: random, value: '32'.ether });
344+
// Wait for scrub period
345+
await helpers.time.increase(60 * 60 * 12 + 1)
346+
// Stake
347+
await stakeMinipool(minipool1, { from: node })
348+
await stakeMinipool(minipool2, { from: node })
349+
await stakeMinipool(minipool3, { from: node })
350+
// Register trusted nodes
351+
await registerNode({ from: trustedNode1 });
352+
await registerNode({ from: trustedNode2 });
353+
await registerNode({ from: trustedNode3 });
354+
await setNodeTrusted(trustedNode1, 'saas_1', 'node@home.com', owner);
355+
await setNodeTrusted(trustedNode2, 'saas_2', 'node@home.com', owner);
356+
await setNodeTrusted(trustedNode3, 'saas_3', 'node@home.com', owner);
357+
// Set max penalty rate
358+
let rocketMinipoolPenalty = await RocketMinipoolPenalty.deployed();
359+
await rocketMinipoolPenalty.connect(owner).setMaxPenaltyRate('1'.ether);
360+
})
361+
362+
it(printTitle('node', 'can not unstake legacy staked RPL while below minimum after slashing'), async () => {
363+
const rocketNodeStaking = await RocketNodeStaking.deployed()
364+
const stakedRPLBefore = await rocketNodeStaking.getNodeLegacyStakedRPL(node.address)
365+
await withdrawAndCheck(minipool1, '15', node, false, '15', '0');
366+
// Try to unstake 100 RPL which would bring node operator under requirement
367+
await shouldRevert(
368+
unstakeLegacyRpl('100'.ether, { from: node }),
369+
'Was able to unstake while below 15% minimum',
370+
'Insufficient legacy staked RPL'
371+
);
372+
// Exit other 2 validators with further 200 RPL slashing
373+
await withdrawAndCheck(minipool2, '15', node, false, '15', '0');
374+
await withdrawAndCheck(minipool3, '15', node, false, '15', '0');
375+
// User should have been slashed 300 RPL in total
376+
const stakedRPLAfter = await rocketNodeStaking.getNodeLegacyStakedRPL(node.address)
377+
assertBN.equal(stakedRPLBefore - stakedRPLAfter, '300'.ether);
378+
// Withdraw remaining
379+
await unstakeLegacyRpl(stakedRPLAfter, { from: node })
380+
});
381+
})
303382
});
304383
}

0 commit comments

Comments
 (0)