Skip to content

Commit 9f91edc

Browse files
committed
Merge branch 'upgrade-guardrails' into v1.4
2 parents b7f986a + d423c09 commit 9f91edc

16 files changed

+567
-55
lines changed

contracts/contract/dao/node/RocketDAONodeTrustedUpgrade.sol

Lines changed: 163 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,125 @@
1-
pragma solidity 0.7.6;
2-
31
// SPDX-License-Identifier: GPL-3.0-only
2+
pragma solidity 0.8.30;
43

5-
import "../../RocketBase.sol";
6-
import "../../../interface/dao/node/RocketDAONodeTrustedUpgradeInterface.sol";
7-
import "../../../interface/util/IERC20.sol";
8-
9-
// Handles network contract upgrades
4+
import {RocketStorageInterface} from "../../../interface/RocketStorageInterface.sol";
5+
import {RocketDAONodeTrustedUpgradeInterface} from "../../../interface/dao/node/RocketDAONodeTrustedUpgradeInterface.sol";
6+
import {RocketDAOProtocolSettingsSecurityInterface} from "../../../interface/dao/protocol/settings/RocketDAOProtocolSettingsSecurityInterface.sol";
7+
import {RocketBase} from "../../RocketBase.sol";
108

9+
/// @notice Handles network contract upgrades
1110
contract RocketDAONodeTrustedUpgrade is RocketBase, RocketDAONodeTrustedUpgradeInterface {
12-
1311
// Events
12+
event UpgradePending(uint256 upgradeProposalID, bytes32 indexed upgradeType, bytes32 indexed name, uint256 time);
13+
event UpgradeVetoed(uint256 upgradeProposalID, uint256 time);
1414
event ContractUpgraded(bytes32 indexed name, address indexed oldAddress, address indexed newAddress, uint256 time);
1515
event ContractAdded(bytes32 indexed name, address indexed newAddress, uint256 time);
1616
event ABIUpgraded(bytes32 indexed name, uint256 time);
1717
event ABIAdded(bytes32 indexed name, uint256 time);
1818

19+
// The namespace for any storage data used by this contract
20+
string constant private daoUpgradeNameSpace = "dao.upgrade.";
21+
22+
// Immutables
23+
bytes32 immutable internal daoTrustedBootstrapKey;
24+
bytes32 immutable internal typeUpgradeContract;
25+
bytes32 immutable internal typeAddContract;
26+
bytes32 immutable internal typeUpgradeABI;
27+
bytes32 immutable internal typeAddABI;
28+
29+
// Only allow bootstrapping when enabled
30+
modifier onlyBootstrapMode() {
31+
require(getBool(daoTrustedBootstrapKey) == false, "Bootstrap mode not engaged");
32+
_;
33+
}
34+
1935
// Construct
2036
constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
21-
version = 1;
37+
version = 2;
38+
39+
// Precompute keys
40+
typeUpgradeContract = keccak256(abi.encodePacked("upgradeContract"));
41+
typeAddContract = keccak256(abi.encodePacked("addContract"));
42+
typeUpgradeABI = keccak256(abi.encodePacked("upgradeABI"));
43+
typeAddABI = keccak256(abi.encodePacked("addABI"));
44+
daoTrustedBootstrapKey = keccak256(abi.encodePacked("dao.trustednodes.", "bootstrapmode.disabled"));
2245
}
2346

24-
// Main accessor for performing an upgrade, be it a contract or abi for a contract
25-
// Will require > 50% of trusted DAO members to run when bootstrap mode is disabled
47+
/// @notice Called when an upgrade proposal is executed, creates an upgrade proposal that can be vetoed by the
48+
/// security council or executed after the upgrade delay period has passed
49+
/// @param _type Type of upgrade (valid values: "upgradeContract", "addContract", "upgradeABI", "addABI")
50+
/// @param _name Contract name to upgrade
51+
/// @param _contractAbi ABI of the upgraded contract
52+
/// @param _contractAddress Address of the upgraded contract
2653
function upgrade(string memory _type, string memory _name, string memory _contractAbi, address _contractAddress) override external onlyLatestContract("rocketDAONodeTrustedProposals", msg.sender) {
27-
// What action are we performing?
54+
uint256 upgradeProposalID = getTotal() + 1;
55+
// Compute when the proposal can be executed if not vetoed by the security council
56+
uint256 startTime = block.timestamp;
57+
RocketDAOProtocolSettingsSecurityInterface rocketDAOProtocolSettingsSecurity = RocketDAOProtocolSettingsSecurityInterface(getContractAddress("rocketDAOProtocolSettingsSecurity"));
58+
uint256 endTime = startTime + rocketDAOProtocolSettingsSecurity.getUpgradeDelay();
59+
// Store data
2860
bytes32 typeHash = keccak256(abi.encodePacked(_type));
29-
// Lets do it!
30-
if(typeHash == keccak256(abi.encodePacked("upgradeContract"))) _upgradeContract(_name, _contractAddress, _contractAbi);
31-
if(typeHash == keccak256(abi.encodePacked("addContract"))) _addContract(_name, _contractAddress, _contractAbi);
32-
if(typeHash == keccak256(abi.encodePacked("upgradeABI"))) _upgradeABI(_name, _contractAbi);
33-
if(typeHash == keccak256(abi.encodePacked("addABI"))) _addABI(_name, _contractAbi);
61+
setBytes32(keccak256(abi.encodePacked(daoUpgradeNameSpace, "type", upgradeProposalID)), typeHash);
62+
setString(keccak256(abi.encodePacked(daoUpgradeNameSpace, "name", upgradeProposalID)), _name);
63+
setString(keccak256(abi.encodePacked(daoUpgradeNameSpace, "abi", upgradeProposalID)), _contractAbi);
64+
setAddress(keccak256(abi.encodePacked(daoUpgradeNameSpace, "address", upgradeProposalID)), _contractAddress);
65+
setUint(keccak256(abi.encodePacked(daoUpgradeNameSpace, "end", upgradeProposalID)), endTime);
66+
addUint(keccak256(abi.encodePacked(daoUpgradeNameSpace, "total")), 1);
67+
// Emit event
68+
emit UpgradePending(upgradeProposalID, typeHash, keccak256(abi.encodePacked(_name)), block.timestamp);
3469
}
3570

71+
/// @notice Called by the proposal contract when a veto passes
72+
/// @param _upgradeProposalID ID of the upgrade proposal to veto
73+
function veto(uint256 _upgradeProposalID) override external onlyLatestContract("rocketDAOSecurityUpgrade", msg.sender) {
74+
// Validate proposal state
75+
require(getState(_upgradeProposalID) == UpgradeProposalState.Pending, "Proposal has already succeeded, expired, or executed");
76+
// Mark the upgrade as vetoed
77+
setBool(keccak256(abi.encodePacked(daoUpgradeNameSpace, "vetoed", _upgradeProposalID)), true);
78+
// Emit event
79+
emit UpgradeVetoed(_upgradeProposalID, block.timestamp);
80+
}
81+
82+
/// @notice Called after upgrade delay has passed to perform the upgrade
83+
/// @param _upgradeProposalID ID of the upgrade proposal to execute
84+
/// @dev Must be called by a registered trusted node
85+
function execute(uint256 _upgradeProposalID) override external onlyTrustedNode(msg.sender) {
86+
// Validate proposal state
87+
require(getState(_upgradeProposalID) == UpgradeProposalState.Succeeded, "Proposal has not succeeded or has been vetoed or executed");
88+
// Mark as executed
89+
setBool(keccak256(abi.encodePacked(daoUpgradeNameSpace, "executed", _upgradeProposalID)), true);
90+
// Execute the upgrade
91+
_execute(getType(_upgradeProposalID), getName(_upgradeProposalID), getUpgradeABI(_upgradeProposalID), getUpgradeAddress(_upgradeProposalID));
92+
}
93+
94+
/// @notice Immediately execute an upgrade if bootstrap mode is still enabled
95+
/// @param _type Type of upgrade (valid values: "upgradeContract", "addContract", "upgradeABI", "addABI")
96+
/// @param _name Contract name to upgrade
97+
/// @param _contractAbi ABI of the upgraded contract
98+
/// @param _contractAddress Address of the upgraded contract
99+
function bootstrapUpgrade(string memory _type, string memory _name, string memory _contractAbi, address _contractAddress) override external onlyGuardian onlyBootstrapMode {
100+
bytes32 typeHash = keccak256(abi.encodePacked(_type));
101+
_execute(typeHash, _name, _contractAbi, _contractAddress);
102+
}
36103

37-
/*** Internal Upgrade Methods for the Trusted Node DAO ****************/
104+
/// @dev Internal implementation of the execution process
105+
function _execute(bytes32 _typeHash, string memory _name, string memory _contractAbi, address _contractAddress) internal {
106+
if (_typeHash == typeUpgradeContract) _upgradeContract(_name, _contractAddress, _contractAbi);
107+
else if (_typeHash == typeAddContract) _addContract(_name, _contractAddress, _contractAbi);
108+
else if (_typeHash == typeUpgradeABI) _upgradeABI(_name, _contractAbi);
109+
else if (_typeHash == typeAddABI) _addABI(_name, _contractAbi);
110+
else revert("Invalid upgrade type");
111+
}
38112

39-
// Upgrade a network contract
113+
/// @dev Performs an update to a contract and ABI simultaneously
40114
function _upgradeContract(string memory _name, address _contractAddress, string memory _contractAbi) internal {
41115
// Check contract being upgraded
42116
bytes32 nameHash = keccak256(abi.encodePacked(_name));
43-
require(nameHash != keccak256(abi.encodePacked("rocketVault")), "Cannot upgrade the vault");
44-
require(nameHash != keccak256(abi.encodePacked("rocketTokenRETH")), "Cannot upgrade token contracts");
45-
require(nameHash != keccak256(abi.encodePacked("rocketTokenRPL")), "Cannot upgrade token contracts");
117+
require(nameHash != keccak256(abi.encodePacked("rocketVault")), "Cannot upgrade the vault");
118+
require(nameHash != keccak256(abi.encodePacked("rocketTokenRETH")), "Cannot upgrade token contracts");
119+
require(nameHash != keccak256(abi.encodePacked("rocketTokenRPL")), "Cannot upgrade token contracts");
46120
require(nameHash != keccak256(abi.encodePacked("rocketTokenRPLFixedSupply")), "Cannot upgrade token contracts");
47-
require(nameHash != keccak256(abi.encodePacked("casperDeposit")), "Cannot upgrade the casper deposit contract");
48-
require(nameHash != keccak256(abi.encodePacked("rocketMinipoolPenalty")), "Cannot upgrade minipool penalty contract");
121+
require(nameHash != keccak256(abi.encodePacked("casperDeposit")), "Cannot upgrade the casper deposit contract");
122+
require(nameHash != keccak256(abi.encodePacked("rocketMinipoolPenalty")), "Cannot upgrade minipool penalty contract");
49123
// Get old contract address & check contract exists
50124
address oldContractAddress = getAddress(keccak256(abi.encodePacked("contract.address", _name)));
51125
require(oldContractAddress != address(0x0), "Contract does not exist");
@@ -67,7 +141,7 @@ contract RocketDAONodeTrustedUpgrade is RocketBase, RocketDAONodeTrustedUpgradeI
67141
emit ContractUpgraded(nameHash, oldContractAddress, _contractAddress, block.timestamp);
68142
}
69143

70-
// Add a new network contract
144+
/// @dev Adds a new contract to the protocol
71145
function _addContract(string memory _name, address _contractAddress, string memory _contractAbi) internal {
72146
// Check contract name
73147
bytes32 nameHash = keccak256(abi.encodePacked(_name));
@@ -91,7 +165,7 @@ contract RocketDAONodeTrustedUpgrade is RocketBase, RocketDAONodeTrustedUpgradeI
91165
emit ContractAdded(nameHash, _contractAddress, block.timestamp);
92166
}
93167

94-
// Upgrade a network contract ABI
168+
/// @dev Upgrades an existing ABI
95169
function _upgradeABI(string memory _name, string memory _contractAbi) internal {
96170
// Check ABI exists
97171
string memory existingAbi = getString(keccak256(abi.encodePacked("contract.abi", _name)));
@@ -105,7 +179,7 @@ contract RocketDAONodeTrustedUpgrade is RocketBase, RocketDAONodeTrustedUpgradeI
105179
emit ABIUpgraded(keccak256(abi.encodePacked(_name)), block.timestamp);
106180
}
107181

108-
// Add a new network contract ABI
182+
/// @dev Adds a new ABI to the protocol
109183
function _addABI(string memory _name, string memory _contractAbi) internal {
110184
// Check ABI name
111185
bytes32 nameHash = keccak256(abi.encodePacked(_name));
@@ -123,4 +197,66 @@ contract RocketDAONodeTrustedUpgrade is RocketBase, RocketDAONodeTrustedUpgradeI
123197
emit ABIAdded(nameHash, block.timestamp);
124198
}
125199

200+
/// @notice Get the total number of upgrade proposals
201+
function getTotal() override public view returns (uint256) {
202+
return getUint(keccak256(abi.encodePacked(daoUpgradeNameSpace, "total")));
203+
}
204+
205+
/// @notice Return the state of the specified upgrade proposal
206+
/// @param _upgradeProposalID ID of the upgrade proposal to query
207+
function getState(uint256 _upgradeProposalID) override public view returns (UpgradeProposalState) {
208+
// Check the proposal ID is legit
209+
require(getTotal() >= _upgradeProposalID && _upgradeProposalID > 0, "Invalid upgrade proposal ID");
210+
if (getVetoed(_upgradeProposalID)) {
211+
return UpgradeProposalState.Vetoed;
212+
} else if (getExecuted(_upgradeProposalID)) {
213+
return UpgradeProposalState.Executed;
214+
} else if (block.timestamp < getEnd(_upgradeProposalID)) {
215+
return UpgradeProposalState.Pending;
216+
} else {
217+
return UpgradeProposalState.Succeeded;
218+
}
219+
}
220+
221+
/// @notice Get the end time of this proposal (when the upgrade delay ends)
222+
/// @param _upgradeProposalID ID of the upgrade proposal to query
223+
function getEnd(uint256 _upgradeProposalID) override public view returns (uint256) {
224+
return getUint(keccak256(abi.encodePacked(daoUpgradeNameSpace, "end", _upgradeProposalID)));
225+
}
226+
227+
/// @notice Get whether the proposal has been executed
228+
/// @param _upgradeProposalID ID of the upgrade proposal to query
229+
function getExecuted(uint256 _upgradeProposalID) override public view returns (bool) {
230+
return getBool(keccak256(abi.encodePacked(daoUpgradeNameSpace, "executed", _upgradeProposalID)));
231+
}
232+
233+
/// @notice Get whether the proposal has been vetoed
234+
/// @param _upgradeProposalID ID of the upgrade proposal to query
235+
function getVetoed(uint256 _upgradeProposalID) override public view returns (bool) {
236+
return getBool(keccak256(abi.encodePacked(daoUpgradeNameSpace, "vetoed", _upgradeProposalID)));
237+
}
238+
239+
/// @notice Get the proposal type
240+
/// @param _upgradeProposalID ID of the upgrade proposal to query
241+
function getType(uint256 _upgradeProposalID) override public view returns (bytes32) {
242+
return getBytes32(keccak256(abi.encodePacked(daoUpgradeNameSpace, "type", _upgradeProposalID)));
243+
}
244+
245+
/// @notice Get the proposed upgrade contract name
246+
/// @param _upgradeProposalID ID of the upgrade proposal to query
247+
function getName(uint256 _upgradeProposalID) override public view returns (string memory) {
248+
return getString(keccak256(abi.encodePacked(daoUpgradeNameSpace, "name", _upgradeProposalID)));
249+
}
250+
251+
/// @notice Get the proposed upgrade contract address
252+
/// @param _upgradeProposalID ID of the upgrade proposal to query
253+
function getUpgradeAddress(uint256 _upgradeProposalID) override public view returns (address) {
254+
return getAddress(keccak256(abi.encodePacked(daoUpgradeNameSpace, "address", _upgradeProposalID)));
255+
}
256+
257+
/// @notice Get the proposed upgrade contract ABI
258+
/// @param _upgradeProposalID ID of the upgrade proposal to query
259+
function getUpgradeABI(uint256 _upgradeProposalID) override public view returns (string memory) {
260+
return getString(keccak256(abi.encodePacked(daoUpgradeNameSpace, "abi", _upgradeProposalID)));
261+
}
126262
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ contract RocketDAOProtocolSettingsSecurity is RocketDAOProtocolSettings, RocketD
1818
_setSettingUint("proposal.vote.time", 2 weeks); // How long a proposal can be voted on
1919
_setSettingUint("proposal.execute.time", 4 weeks); // How long a proposal can be executed after its voting period is finished
2020
_setSettingUint("proposal.action.time", 4 weeks); // Certain proposals require a secondary action to be run after the proposal is successful (joining, leaving etc). This is how long until that action expires
21+
_setSettingUint("upgradeveto.quorum", 0.33 ether); // RPIP-60: Member quorum threshold to veto a protocol upgrade (33%)
22+
_setSettingUint("upgrade.delay", 7 days); // RPIP-60: Amount of time after an upgrade proposal passes that the security has to veto it
2123
// Default permissions for security council
2224
setBool(keccak256(abi.encodePacked("dao.security.allowed.setting", "deposit", "deposit.enabled")), true);
2325
setBool(keccak256(abi.encodePacked("dao.security.allowed.setting", "deposit", "deposit.assign.enabled")), true);
@@ -92,4 +94,14 @@ contract RocketDAOProtocolSettingsSecurity is RocketDAOProtocolSettings, RocketD
9294
function getActionTime() override external view returns (uint256) {
9395
return getSettingUint("proposal.action.time");
9496
}
97+
98+
/// @notice The quorum required by the security council to veto an upgrade
99+
function getUpgradeVetoQuorum() override external view returns (uint256) {
100+
return getSettingUint("upgradeveto.quorum");
101+
}
102+
103+
/// @notice The amount of time that must be waited after an upgrade before executing
104+
function getUpgradeDelay() override external view returns (uint256) {
105+
return getSettingUint("upgrade.delay");
106+
}
95107
}

contracts/contract/dao/security/RocketDAOSecurityProposals.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ contract RocketDAOSecurityProposals is RocketBase, RocketDAOSecurityProposalsInt
4545
}
4646

4747
constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
48-
version = 1;
48+
version = 2;
4949
}
5050

5151
/// @notice Creates a new proposal for this DAO

0 commit comments

Comments
 (0)