Skip to content

Commit 3975a26

Browse files
committed
Prevent underflow in newValidator when underbonded by > 32 ETH
1 parent 6689cda commit 3975a26

File tree

3 files changed

+60
-13
lines changed

3 files changed

+60
-13
lines changed

contracts/contract/megapool/RocketMegapoolDelegate.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,15 @@ contract RocketMegapoolDelegate is RocketMegapoolDelegateBase, RocketMegapoolDel
173173
uint256 newBondRequirement = rocketNodeDeposit.getBondRequirement(getActiveValidatorCount() + 1);
174174
uint256 effectiveBond = nodeBond + nodeQueuedBond;
175175
if (newBondRequirement > effectiveBond) {
176+
// Clamp new bond requirement between 1 - 32 ETH
176177
if (newBondRequirement - effectiveBond < prestakeValue) {
177178
return prestakeValue;
178179
} else {
179-
return newBondRequirement - effectiveBond;
180+
uint256 bondRequirement = newBondRequirement - effectiveBond;
181+
if (bondRequirement > fullDepositValue) {
182+
bondRequirement = fullDepositValue;
183+
}
184+
return bondRequirement;
180185
}
181186
} else {
182187
return prestakeValue;

test/dao/dao-node-trusted-tests.js

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, before } from 'mocha';
1+
import { before, describe, it } from 'mocha';
22
import { printTitle } from '../_utils/formatting';
33
import { shouldRevert } from '../_utils/testing';
44
import { compressABI } from '../_utils/contract';
@@ -34,11 +34,14 @@ import {
3434
import { assertBN } from '../_helpers/bn';
3535
import {
3636
RocketDAONodeTrusted,
37-
RocketDAONodeTrustedActions, RocketDAONodeTrustedProposals,
37+
RocketDAONodeTrustedActions,
38+
RocketDAONodeTrustedProposals,
3839
RocketDAONodeTrustedSettingsMembers,
3940
RocketDAONodeTrustedSettingsProposals,
4041
RocketDAONodeTrustedUpgrade,
41-
RocketDAOProtocolSettingsMegapool, RocketDAOProtocolSettingsNode, RocketDAOProtocolSettingsSecurity,
42+
RocketDAOProtocolSettingsMegapool,
43+
RocketDAOProtocolSettingsNode,
44+
RocketDAOProtocolSettingsSecurity,
4245
RocketMinipoolManager,
4346
RocketStorage,
4447
RocketTokenRPL,
@@ -853,8 +856,8 @@ export default function() {
853856
const address = await rocketDAONodeTrustedUpgrade.getUpgradeAddress(1n);
854857
const expectedType = ethers.solidityPackedKeccak256(['string'], ['upgradeContract']);
855858
assert.equal(address, rocketMinipoolManagerNew.target);
856-
assert.equal(type, expectedType)
857-
assert.equal(name, 'rocketNodeManager')
859+
assert.equal(type, expectedType);
860+
assert.equal(name, 'rocketNodeManager');
858861
// Upgrade should fail before delay
859862
await shouldRevert(
860863
rocketDAONodeTrustedUpgrade.connect(registeredNodeTrusted1).execute(1n),
@@ -966,22 +969,22 @@ export default function() {
966969
});
967970

968971
it(printTitle('guardian', 'can not set "reduced.bond" to a value not divisible by milliwei'), async () => {
969-
// Can set to 1 ether + 1 milliwei
970-
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNode, 'reduced.bond', 1001000000000000000n, { from: guardian });
972+
// Can set to 2 ether + 1 milliwei
973+
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNode, 'reduced.bond', 2001000000000000000n, { from: guardian });
971974
// Cannot set to 1 ether + 1 milliwei + 1 microwei
972975
await shouldRevert(
973976
setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNode, 'reduced.bond', 1001001000000000000n, { from: guardian }),
974977
'Was able to set "reduced.bond" to value not divisible by milliwei',
975-
'Value must be divisible by milliwei'
976-
)
978+
'Value must be divisible by milliwei',
979+
);
977980
});
978981

979982
it(printTitle('guardian', 'can not set "megapool.dissolve.penalty" to zero'), async () => {
980983
await shouldRevert(
981984
setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsMegapool, 'megapool.dissolve.penalty', 0n, { from: guardian }),
982985
'Was able to set "megapool.dissolve.penalty" to zero',
983-
'Value must be >= 0.01 ETH'
984-
)
986+
'Value must be >= 0.01 ETH',
987+
);
985988
});
986989

987990
it(printTitle('guardian', 'can add a contract ABI in bootstrap mode'), async () => {

test/megapool/megapool-tests.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,44 @@ export default function() {
994994
await nodeDeposit(node, '8'.ether);
995995
});
996996

997+
it(printTitle('node', 'can create a new validator if bond requirement increases and node is underbonded by > 32 ETH'), async () => {
998+
const dissolvePeriod = (60 * 60 * 24 * 10); // 10 Days
999+
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsMegapool, 'megapool.time.before.dissolve', dissolvePeriod, { from: owner });
1000+
// Reduce reduced.bond to 2 ETH
1001+
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNode, 'reduced.bond', '2'.ether, { from: owner });
1002+
// Increase deposit pool capacity
1003+
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsDeposit, 'deposit.pool.maximum', '10000'.ether, { from: owner });
1004+
// Set penalty to 0.1 ETH
1005+
const dissolvePenalty = '0.1'.ether;
1006+
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsMegapool, 'megapool.dissolve.penalty', dissolvePenalty, { from: owner });
1007+
// Deposit ETH
1008+
await userDeposit({ from: random, value: '30'.ether * 35n });
1009+
// Make 32 validators with 2 ETH bond
1010+
await nodeDeposit(node, '4'.ether);
1011+
await nodeDeposit(node, '4'.ether);
1012+
for (let i = 0n; i < 30n; i++) {
1013+
await nodeDeposit(node, '2'.ether);
1014+
}
1015+
// Node should now have 32 active validators with a bond of 4+4+(2*30) = 68ETH
1016+
assertBN.equal(await megapool.getNodeBond(), '68'.ether);
1017+
assertBN.equal(await megapool.getUserCapital(), '32'.ether * 32n - '68'.ether);
1018+
assertBN.equal(await megapool.getNodeQueuedBond(), '0'.ether);
1019+
assertBN.equal(await megapool.getUserQueuedCapital(), '0'.ether);
1020+
// Increase reduced.bond back to 4 ETH
1021+
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNode, 'reduced.bond', '4'.ether, { from: owner });
1022+
// Node is now underbonded by 4+4+(4*30)-68 = 60 ETH so next validator must have 32 ETH bond
1023+
assertBN.equal(await megapool.getNewValidatorBondRequirement(), '32'.ether);
1024+
await nodeDeposit(node, '32'.ether);
1025+
// Node now has bond of 68+32 = 100, bond requirement with new validator is is 4+4+(4*32) = 136 ETH, so next validator requires 32 ETH
1026+
assertBN.equal(await megapool.getNodeBond(), '100'.ether);
1027+
assertBN.equal(await megapool.getNewValidatorBondRequirement(), '32'.ether);
1028+
await nodeDeposit(node, '32'.ether);
1029+
// Node now has bond of 100 + 32 = 132, bond requirement with new validator is 4+4+(4*33) = 140 ETH, so next validator requires 8 ETH
1030+
assertBN.equal(await megapool.getNodeBond(), '132'.ether);
1031+
assertBN.equal(await megapool.getNewValidatorBondRequirement(), '8'.ether);
1032+
await nodeDeposit(node, '8'.ether);
1033+
});
1034+
9971035
it(printTitle('node', 'can dissolve and exit validators when underbonded'), async () => {
9981036
const dissolvePeriod = (60 * 60 * 24 * 10); // 10 Days
9991037
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsMegapool, 'megapool.time.before.dissolve', dissolvePeriod, { from: owner });
@@ -1074,7 +1112,8 @@ export default function() {
10741112
}
10751113
assertBN.equal(await megapool.getNodeBond(), '0'.ether);
10761114
assertBN.equal(await megapool.getUserCapital(), '0'.ether);
1077-
});
1115+
})
1116+
10781117

10791118
it(printTitle('node', 'cannot exit queue to underbonded state due to dissolves'), async () => {
10801119
const dissolvePeriod = (60 * 60 * 24 * 10); // 10 Days

0 commit comments

Comments
 (0)