Skip to content

Commit cd3a2f2

Browse files
committed
Implement RPIP-61
1 parent 84ae5df commit cd3a2f2

File tree

6 files changed

+104
-12
lines changed

6 files changed

+104
-12
lines changed

contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsNetwork.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ contract RocketDAOProtocolSettingsNetwork is RocketDAOProtocolSettings, RocketDA
3737
_setSettingUint("network.node.commission.share.security.council.adder", 0 ether); // 0% (RPIP-46)
3838
_setSettingUint("network.voter.share", 0.09 ether); // 9% (RPIP-46)
3939
_setSettingUint("network.max.node.commission.share.council.adder", 0.01 ether); // 1% (RPIP-46)
40+
_setSettingUint("network.max.reth.balance.delta", 0.02 ether); // 2% (RPIP-61)
4041
// Set deploy flag
4142
setBool(keccak256(abi.encodePacked(settingNameSpace, "deployed")), true);
4243
}
@@ -57,6 +58,9 @@ contract RocketDAOProtocolSettingsNetwork is RocketDAOProtocolSettings, RocketDA
5758
require(_value >= 0.05 ether && _value <= 0.2 ether, "The node fee maximum must be a value between 5% and 20%");
5859
} else if (settingKey == keccak256(bytes("network.submit.balances.frequency"))) {
5960
require(_value >= 1 hours, "The submit frequency must be >= 1 hour");
61+
} else if (settingKey == keccak256(bytes("network.max.reth.balance.delta"))) {
62+
// RPIP-61 guardrail
63+
require(_value >= 0.01 ether, "The max rETH balance delta must be >= 1%");
6064
} else if (settingKey == keccak256(bytes("network.node.commission.share.security.council.adder"))) {
6165
return _setNodeShareSecurityCouncilAdder(_value);
6266
} else if (settingKey == keccak256(bytes("network.node.commission.share"))) {
@@ -185,6 +189,11 @@ contract RocketDAOProtocolSettingsNetwork is RocketDAOProtocolSettings, RocketDA
185189
return getSettingAddressList("network.allow.listed.controllers");
186190
}
187191

192+
/// @notice Returns the maximum amount rETH balance deltas can be changed per submission (as a percentage of 1e18)
193+
function getMaxRethDelta() override external view returns (uint256) {
194+
return getSettingUint("network.max.reth.balance.delta");
195+
}
196+
188197
/// @notice Returns true if the supplied address is one of the allow listed controllers
189198
/// @param _address The address to check for on the allow list
190199
function isAllowListedController(address _address) override public view returns (bool) {

contracts/contract/network/RocketNetworkBalances.sol

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ contract RocketNetworkBalances is RocketBase, RocketNetworkBalancesInterface {
1515
event BalancesUpdated(uint256 indexed block, uint256 slotTimestamp, uint256 totalEth, uint256 stakingEth, uint256 rethSupply, uint256 blockTimestamp);
1616

1717
constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
18-
version = 3;
18+
version = 4;
1919
}
2020

2121
/// @notice The block number which balances are current for
@@ -28,6 +28,16 @@ contract RocketNetworkBalances is RocketBase, RocketNetworkBalancesInterface {
2828
setUint(keccak256("network.balances.updated.block"), _value);
2929
}
3030

31+
/// @notice Get the timestamp for the last balance update
32+
function getBalancesTimestamp() override public view returns (uint256) {
33+
return getUint(keccak256("network.balances.updated.timestamp"));
34+
}
35+
36+
/// @notice Sets the timestamp of the last balance update
37+
function setBalancesTimestamp(uint256 _value) private {
38+
setUint(keccak256("network.balances.updated.timestamp"), _value);
39+
}
40+
3141
/// @notice The current RP network total ETH balance
3242
function getTotalETHBalance() override public view returns (uint256) {
3343
return getUint(keccak256("network.balance.total"));
@@ -122,6 +132,27 @@ contract RocketNetworkBalances is RocketBase, RocketNetworkBalancesInterface {
122132

123133
/// @dev Internal method to update network balances
124134
function updateBalances(uint256 _block, uint256 _slotTimestamp, uint256 _totalEth, uint256 _stakingEth, uint256 _rethSupply) private {
135+
// Check enough time has passed (RPIP-61)
136+
RocketDAOProtocolSettingsNetworkInterface rocketDAOProtocolSettingsNetwork = RocketDAOProtocolSettingsNetworkInterface(getContractAddress("rocketDAOProtocolSettingsNetwork"));
137+
uint256 frequency = rocketDAOProtocolSettingsNetwork.getSubmitBalancesFrequency();
138+
uint256 lastTimestamp = getBalancesTimestamp();
139+
uint256 minimumTimestamp = lastTimestamp + (frequency * 95 / 100);
140+
require(block.timestamp >= minimumTimestamp, "Not enough time has passed");
141+
setBalancesTimestamp(block.timestamp);
142+
// Check rETH delta is within allowed range (RPIP-61)
143+
uint256 currentTotalEthBalance = getTotalETHBalance();
144+
// Bypass the delta restriction on first balance update
145+
if (currentTotalEthBalance > 0) {
146+
uint256 maxChangePercent = rocketDAOProtocolSettingsNetwork.getMaxRethDelta();
147+
uint256 maxChange = currentTotalEthBalance * maxChangePercent / calcBase;
148+
// TODO: RPIP-61 states "If an update would lead to an rETH exchange rate change of more than Maximum rETH Delta, the oDAO SHALL submit an update of Maximum rETH Delta instead."
149+
// TODO: Determine whether this should be performed off chain or applied here
150+
if (_totalEth > currentTotalEthBalance) {
151+
require(_totalEth - currentTotalEthBalance <= maxChange, "Change exceeds allowed range");
152+
} else {
153+
require(currentTotalEthBalance - _totalEth <= maxChange, "Change exceeds allowed range");
154+
}
155+
}
125156
// Update balances
126157
setBalancesBlock(_block);
127158
setTotalETHBalance(_totalEth);

contracts/contract/upgrade/RocketUpgradeOneDotFour.sol

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ contract RocketUpgradeOneDotFour is RocketBase {
3737
address public rocketDAOProtocolSettingsMegapool;
3838
address public rocketDAOSecurityProposals;
3939
address public rocketNetworkRevenues;
40+
address public rocketNetworkBalances;
4041
address public rocketNetworkSnapshots;
4142
address public rocketVoterRewards;
4243
address public blockRoots;
@@ -62,6 +63,7 @@ contract RocketUpgradeOneDotFour is RocketBase {
6263
string public rocketDAOProtocolSettingsMegapoolAbi;
6364
string public rocketDAOSecurityProposalsAbi;
6465
string public rocketNetworkRevenuesAbi;
66+
string public rocketNetworkBalancesAbi;
6567
string public rocketNetworkSnapshotsAbi;
6668
string public rocketVoterRewardsAbi;
6769
string public blockRootsAbi;
@@ -108,11 +110,12 @@ contract RocketUpgradeOneDotFour is RocketBase {
108110
rocketDAOProtocolSettingsMegapool = _addresses[15];
109111
rocketDAOSecurityProposals = _addresses[16];
110112
rocketNetworkRevenues = _addresses[17];
111-
rocketNetworkSnapshots = _addresses[18];
112-
rocketVoterRewards = _addresses[19];
113-
blockRoots = _addresses[20];
114-
beaconStateVerifier = _addresses[21];
115-
rocketNodeDistributorDelegate = _addresses[22];
113+
rocketNetworkBalances = _addresses[18];
114+
rocketNetworkSnapshots = _addresses[19];
115+
rocketVoterRewards = _addresses[20];
116+
blockRoots = _addresses[21];
117+
beaconStateVerifier = _addresses[22];
118+
rocketNodeDistributorDelegate = _addresses[23];
116119

117120
// Set ABIs
118121
rocketMegapoolDelegateAbi = _abis[0];
@@ -133,11 +136,12 @@ contract RocketUpgradeOneDotFour is RocketBase {
133136
rocketDAOProtocolSettingsMegapoolAbi = _abis[15];
134137
rocketDAOSecurityProposalsAbi = _abis[16];
135138
rocketNetworkRevenuesAbi = _abis[17];
136-
rocketNetworkSnapshotsAbi = _abis[18];
137-
rocketVoterRewardsAbi = _abis[19];
138-
blockRootsAbi = _abis[20];
139-
beaconStateVerifierAbi = _abis[21];
140-
rocketNodeDistributorDelegateAbi = _abis[22];
139+
rocketNetworkBalancesAbi = _abis[18];
140+
rocketNetworkSnapshotsAbi = _abis[19];
141+
rocketVoterRewardsAbi = _abis[20];
142+
blockRootsAbi = _abis[21];
143+
beaconStateVerifierAbi = _abis[22];
144+
rocketNodeDistributorDelegateAbi = _abis[23];
141145
}
142146

143147
/// @notice Prevents further changes from being applied
@@ -167,6 +171,7 @@ contract RocketUpgradeOneDotFour is RocketBase {
167171
_upgradeContract("rocketNodeManager", rocketNodeManager, rocketNodeManagerAbi);
168172
_upgradeContract("rocketNodeDeposit", rocketNodeDeposit, rocketNodeDepositAbi);
169173
_upgradeContract("rocketNodeStaking", rocketNodeStaking, rocketNodeStakingAbi);
174+
_upgradeContract("rocketNetworkBalances", rocketNetworkBalances, rocketNetworkBalancesAbi);
170175
_upgradeContract("rocketDepositPool", rocketDepositPool, rocketDepositPoolAbi);
171176
_upgradeContract("rocketNetworkSnapshots", rocketNetworkSnapshots, rocketNetworkSnapshotsAbi);
172177
_upgradeContract("rocketDAOProtocolProposals", rocketDAOProtocolProposals, rocketDAOProtocolProposalsAbi);
@@ -200,6 +205,8 @@ contract RocketUpgradeOneDotFour is RocketBase {
200205
setUint(keccak256(abi.encodePacked(settingNameSpace, "network.node.commission.share.security.council.adder")), 0 ether); // 0% (RPIP-46)
201206
setUint(keccak256(abi.encodePacked(settingNameSpace, "network.voter.share")), 0.09 ether); // 9% (RPIP-46)
202207
setUint(keccak256(abi.encodePacked(settingNameSpace, "network.max.node.commission.share.council.adder")), 0.01 ether); // 1% (RPIP-46)
208+
// Initialise max rETH delta per RPIP-61
209+
setUint(keccak256(abi.encodePacked(settingNameSpace, "network.max.reth.balance.delta")), 0.02 ether); // 2% (RPIP-61)
203210

204211
// Initialised reduced_bond and unstaking_period setting per RPIP-42 and RPIP-30
205212
settingNameSpace = keccak256(abi.encodePacked("dao.protocol.setting.", "node"));

contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsNetworkInterface.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface RocketDAOProtocolSettingsNetworkInterface {
2525
function getEffectiveVoterShare() external view returns (uint256);
2626
function getEffectiveNodeShare() external view returns (uint256);
2727
function getAllowListedControllers() external view returns (address[] memory);
28+
function getMaxRethDelta() external view returns (uint256);
2829
function isAllowListedController(address _address) external view returns (bool);
2930
function setNodeShareSecurityCouncilAdder(uint256 _value) external;
3031
function setNodeCommissionShare(uint256 _value) external;

contracts/interface/network/RocketNetworkBalancesInterface.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma abicoder v2;
33
// SPDX-License-Identifier: GPL-3.0-only
44

55
interface RocketNetworkBalancesInterface {
6+
function getBalancesTimestamp() external view returns (uint256);
67
function getBalancesBlock() external view returns (uint256);
78
function getTotalETHBalance() external view returns (uint256);
89
function getStakingETHBalance() external view returns (uint256);

test/network/network-balances-tests.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default function() {
3636
// Constants
3737
const proposalCooldown = 10;
3838
const proposalVoteBlocks = 10;
39+
const submitBalancesFrequency = 3600;
3940

4041
// Setup
4142
before(async () => {
@@ -67,9 +68,16 @@ export default function() {
6768
await setDAONodeTrustedBootstrapSetting(RocketDAONodeTrustedSettingsProposals, 'proposal.vote.blocks', proposalVoteBlocks, { from: owner });
6869
// Set a small vote delay
6970
await setDAONodeTrustedBootstrapSetting(RocketDAONodeTrustedSettingsProposals, 'proposal.vote.delay.blocks', 4, { from: owner });
70-
71+
// Set a smaller submission frequency
72+
await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsNetwork, 'network.submit.balances.frequency', submitBalancesFrequency, { from: owner });
7173
});
7274

75+
async function submitAll(block, slotTimestamp, totalBalance, stakingBalance, rethSupply) {
76+
await submitBalances(block, slotTimestamp, totalBalance, stakingBalance, rethSupply, { from: trustedNode1 });
77+
await submitBalances(block, slotTimestamp, totalBalance, stakingBalance, rethSupply, { from: trustedNode2 });
78+
await submitBalances(block, slotTimestamp, totalBalance, stakingBalance, rethSupply, { from: trustedNode3 });
79+
}
80+
7381
async function trustedNode4JoinDao() {
7482
await registerNode({ from: trustedNode4 });
7583
await setNodeTrusted(trustedNode4, 'saas_4', 'node@home.com', owner);
@@ -212,6 +220,41 @@ export default function() {
212220
});
213221
});
214222

223+
it(printTitle('trusted nodes', 'cannot submit network balances until 95% of submission frequency has passed'), async () => {
224+
// First submission is fine
225+
await submitAll(2, '1600000000', '10'.ether, '9'.ether, '8'.ether);
226+
// Wait only a brief period
227+
await helpers.time.increase(1);
228+
await helpers.mine();
229+
// Submitting should now fail
230+
await shouldRevert(
231+
submitAll(3, '1600000001', '10.1'.ether, '9.1'.ether, '8.1'.ether),
232+
'Was able to submit balances too soon',
233+
'Not enough time has passed',
234+
);
235+
// Wait enough time
236+
await helpers.time.increase(submitBalancesFrequency);
237+
await helpers.mine();
238+
// Submitting should now work
239+
await submitAll(4, '1600000001', '10.1'.ether, '9.1'.ether, '8.1'.ether);
240+
});
241+
242+
it(printTitle('trusted nodes', 'cannot submit network balance change that exceeds 2%'), async () => {
243+
// First submission is fine
244+
await submitAll(2, '1600000000', '10'.ether, '9'.ether, '8'.ether);
245+
// Wait enough time
246+
await helpers.time.increase(submitBalancesFrequency);
247+
await helpers.mine();
248+
// Submitting an increase of 2.1% should fail
249+
await shouldRevert(
250+
submitAll(3, '1600000001', '10.21'.ether, '9.1'.ether, '8.1'.ether),
251+
'Was able to submit balance greater than allowed',
252+
'Change exceeds allowed range'
253+
);
254+
// Submitting a change of 2% should work
255+
await submitAll(3, '1600000001', '10.2'.ether, '9.1'.ether, '8.1'.ether);
256+
});
257+
215258
it(printTitle('trusted nodes', 'cannot submit the same network balances twice'), async () => {
216259
// Set parameters
217260
let block = 1;

0 commit comments

Comments
 (0)