diff --git a/contracts/contract/dao/protocol/RocketDAOProtocol.sol b/contracts/contract/dao/protocol/RocketDAOProtocol.sol index 1eae4075..88cd0216 100644 --- a/contracts/contract/dao/protocol/RocketDAOProtocol.sol +++ b/contracts/contract/dao/protocol/RocketDAOProtocol.sol @@ -22,6 +22,7 @@ contract RocketDAOProtocol is RocketBase, RocketDAOProtocolInterface { event BootstrapSecurityInvite(string id, address memberAddress, uint256 time); event BootstrapSecurityKick(address memberAddress, uint256 time); event BootstrapDisabled(uint256 time); + event BootstrapProtocolDAOEnabled(uint256 block, uint256 time); // The namespace for any data stored in the network DAO (do not change) string constant internal daoNameSpace = "dao.protocol."; @@ -113,8 +114,17 @@ contract RocketDAOProtocol is RocketBase, RocketDAOProtocolInterface { /// @notice Bootstrap mode - Disable RP Access (only RP can call this to hand over full control to the DAO) function bootstrapDisable(bool _confirmDisableBootstrapMode) override external onlyGuardian onlyBootstrapMode onlyLatestContract("rocketDAOProtocol", address(this)) { + // Prevent disabling bootstrap if on-chain governance has not been enabled + require(getUint(keccak256(abi.encodePacked("protocol.dao.enabled.block"))) > 0, "On-chain governance must be enabled first"); + // Disable bootstrap require(_confirmDisableBootstrapMode == true, "You must confirm disabling bootstrap mode, it can only be done once!"); setBool(keccak256(abi.encodePacked(daoNameSpace, "bootstrapmode.disabled")), true); emit BootstrapDisabled(block.timestamp); } + + /// @notice Bootstrap mode - Enables on-chain governance proposals + function bootstrapEnableGovernance() override external onlyGuardian onlyLatestContract("rocketDAOProtocol", address(this)) { + setUint(keccak256(abi.encodePacked("protocol.dao.enabled.block")), block.number); + emit BootstrapProtocolDAOEnabled(block.number, block.timestamp); + } } diff --git a/contracts/contract/dao/protocol/RocketDAOProtocolProposal.sol b/contracts/contract/dao/protocol/RocketDAOProtocolProposal.sol index 589e1a2c..a3f3213d 100644 --- a/contracts/contract/dao/protocol/RocketDAOProtocolProposal.sol +++ b/contracts/contract/dao/protocol/RocketDAOProtocolProposal.sol @@ -37,6 +37,11 @@ contract RocketDAOProtocolProposal is RocketBase, RocketDAOProtocolProposalInter /// @param _blockNumber The block number the proposal is being made for /// @param _treeNodes A merkle pollard generated at _blockNumber for the voting power state of the DAO function propose(string memory _proposalMessage, bytes calldata _payload, uint32 _blockNumber, Types.Node[] calldata _treeNodes) override external onlyRegisteredNode(msg.sender) onlyLatestContract("rocketDAOProtocolProposal", address(this)) returns (uint256) { + // Check on-chain governance has been enabled + { + uint256 enabledBlock = getUint(keccak256(abi.encodePacked("protocol.dao.enabled.block"))); + require(enabledBlock != 0 && _blockNumber >= enabledBlock, "DAO has not been enabled"); + } // Calculate total voting power by summing the pollard uint256 totalVotingPower = 0; uint256 treeNodesLength = _treeNodes.length; diff --git a/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsNode.sol b/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsNode.sol index d0edf393..301bb3d4 100644 --- a/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsNode.sol +++ b/contracts/contract/dao/protocol/settings/RocketDAOProtocolSettingsNode.sol @@ -23,6 +23,7 @@ contract RocketDAOProtocolSettingsNode is RocketDAOProtocolSettings, RocketDAOPr setSettingBool("node.vacant.minipools.enabled", false); setSettingUint("node.per.minipool.stake.minimum", 0.1 ether); // 10% of user ETH value (matched ETH) setSettingUint("node.per.minipool.stake.maximum", 1.5 ether); // 150% of node ETH value (provided ETH) + setSettingUint("node.voting.power.stake.maximum", 1.5 ether); // 150% of node ETH value (provided ETH) // Settings initialised setBool(keccak256(abi.encodePacked(settingNameSpace, "deployed")), true); } @@ -31,7 +32,7 @@ contract RocketDAOProtocolSettingsNode is RocketDAOProtocolSettings, RocketDAOPr /// @notice Update a setting, overrides inherited setting method with extra checks for this contract function setSettingUint(string memory _settingPath, uint256 _value) override public onlyDAOProtocolProposal { bytes32 settingKey = keccak256(bytes(_settingPath)); - if(settingKey == keccak256(bytes("node.per.minipool.stake.maximum"))) { + if(settingKey == keccak256(bytes("node.voting.power.stake.maximum"))) { // Redirect the setting change to push a new value into the snapshot system instead RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots")); rocketNetworkSnapshots.push(settingKey, uint32(block.number), uint224(_value)); @@ -68,9 +69,13 @@ contract RocketDAOProtocolSettingsNode is RocketDAOProtocolSettings, RocketDAOPr // Maximum RPL stake per minipool as a fraction of assigned user ETH value function getMaximumPerMinipoolStake() override external view returns (uint256) { - bytes32 settingKey = keccak256(bytes("node.per.minipool.stake.maximum")); + return getSettingUint("node.per.minipool.stake.maximum"); + } + + // Maximum staked RPL that applies to voting power per minipool as a fraction of assigned user ETH value + function getMaximumStakeForVotingPower() override external view returns (uint256) { + bytes32 settingKey = keccak256(bytes("node.voting.power.stake.maximum")); RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots")); return uint256(rocketNetworkSnapshots.latestValue(settingKey)); } - } diff --git a/contracts/contract/network/RocketNetworkVoting.sol b/contracts/contract/network/RocketNetworkVoting.sol index c0eb8660..2cb14bd7 100644 --- a/contracts/contract/network/RocketNetworkVoting.sol +++ b/contracts/contract/network/RocketNetworkVoting.sol @@ -12,6 +12,7 @@ import "../../interface/network/RocketNetworkPricesInterface.sol"; import "../../interface/minipool/RocketMinipoolManagerInterface.sol"; import "../../interface/util/AddressSetStorageInterface.sol"; import "../../interface/network/RocketNetworkVotingInterface.sol"; +import "../../interface/node/RocketNodeManagerInterface.sol"; /// @notice Accounting for snapshotting of governance related values based on block numbers contract RocketNetworkVoting is RocketBase, RocketNetworkVotingInterface { @@ -28,8 +29,21 @@ contract RocketNetworkVoting is RocketBase, RocketNetworkVotingInterface { priceKey = keccak256("network.prices.rpl"); } - /// @notice Unlocks a node operator's voting power (only required for node operators who registered before governance structure was in place) + /// @notice Unlocks a node operator's voting power (only required for node operators who registered before + /// governance structure was in place). Sets delegate to self. function initialiseVoting() onlyRegisteredNode(msg.sender) external override { + _initialiseVoting(msg.sender); + } + + /// @notice Unlocks a node operator's voting power (only required for node operators who registered before + /// governance structure was in place). + /// @param _delegate The node operator's desired delegate for their voting power + function initialiseVoting(address _delegate) onlyRegisteredNode(msg.sender) onlyRegisteredNode(_delegate) external override { + _initialiseVoting(_delegate); + } + + /// @dev Initialises the snapshot values for the caller to participate in the on-chain governance + function _initialiseVoting(address _delegate) private { // Check if already registered require (!getBool(keccak256(abi.encodePacked("node.voting.enabled", msg.sender))), "Already registered"); setBool(keccak256(abi.encodePacked("node.voting.enabled", msg.sender)), true); @@ -55,7 +69,7 @@ contract RocketNetworkVoting is RocketBase, RocketNetworkVotingInterface { // Set starting delegate to themself key = keccak256(abi.encodePacked("node.delegate", msg.sender)); - rocketNetworkSnapshots.push(key, uint32(block.number), uint224(uint160(msg.sender))); + rocketNetworkSnapshots.push(key, uint32(block.number), uint224(uint160(_delegate))); } function getVotingInitialised(address _nodeAddress) external override view returns (bool) { @@ -109,7 +123,7 @@ contract RocketNetworkVoting is RocketBase, RocketNetworkVotingInterface { uint256 rplStake = uint256(rocketNetworkSnapshots.lookupRecent(key, _block, 5)); // Get RPL max stake percent - key = keccak256(bytes("node.per.minipool.stake.maximum")); + key = keccak256(bytes("node.voting.power.stake.maximum")); uint256 maximumStakePercent = uint256(rocketNetworkSnapshots.lookupRecent(key, _block, 2)); return calculateVotingPower(rplStake, ethProvided, rplPrice, maximumStakePercent); @@ -126,7 +140,7 @@ contract RocketNetworkVoting is RocketBase, RocketNetworkVotingInterface { return Math.sqrt(_rplStake); } - function setDelegate(address _newDelegate) external override onlyRegisteredNode(msg.sender) { + function setDelegate(address _newDelegate) external override onlyRegisteredNode(msg.sender) onlyRegisteredNode(_newDelegate) { RocketNetworkSnapshotsInterface rocketNetworkSnapshots = RocketNetworkSnapshotsInterface(getContractAddress("rocketNetworkSnapshots")); bytes32 key = keccak256(abi.encodePacked("node.delegate", msg.sender)); rocketNetworkSnapshots.push(key, uint32(block.number), uint224(uint160(_newDelegate))); diff --git a/contracts/contract/upgrade/RocketUpgradeOneDotThree.sol b/contracts/contract/upgrade/RocketUpgradeOneDotThree.sol index 6fa5819c..06d65c14 100644 --- a/contracts/contract/upgrade/RocketUpgradeOneDotThree.sol +++ b/contracts/contract/upgrade/RocketUpgradeOneDotThree.sol @@ -5,6 +5,7 @@ import "../RocketBase.sol"; import "../../interface/network/RocketNetworkSnapshotsInterface.sol"; import "../../interface/network/RocketNetworkPricesInterface.sol"; import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol"; +import "../../interface/util/AddressSetStorageInterface.sol"; /// @notice Transient contract to upgrade Rocket Pool with the Houston set of contract upgrades contract RocketUpgradeOneDotThree is RocketBase { @@ -164,9 +165,6 @@ contract RocketUpgradeOneDotThree is RocketBase { require(!executed, "Already executed"); executed = true; - RocketDAOProtocolSettingsNodeInterface rocketDAOProtocolSettingsNode = RocketDAOProtocolSettingsNodeInterface(getContractAddress("rocketDAOProtocolSettingsNode")); - uint224 maxPerMinipoolStake = uint224(rocketDAOProtocolSettingsNode.getMaximumPerMinipoolStake()); - // Upgrade contracts _upgradeContract("rocketDAOProtocol", newRocketDAOProtocol, newRocketDAOProtocolAbi); _upgradeContract("rocketDAOProtocolProposals", newRocketDAOProtocolProposals, newRocketDAOProtocolProposalsAbi); @@ -250,9 +248,14 @@ contract RocketUpgradeOneDotThree is RocketBase { bytes32 snapshotKey = keccak256("network.prices.rpl"); rocketNetworkSnapshots.push(snapshotKey, uint32(block.number), uint224(rocketNetworkPrices.getRPLPrice())); - // Add snapshot entry for maximum RPL stake - snapshotKey = keccak256(bytes("node.per.minipool.stake.maximum")); - rocketNetworkSnapshots.push(snapshotKey, uint32(block.number), maxPerMinipoolStake); + // Add snapshot entry for maximum RPL stake voting power (150%) + snapshotKey = keccak256(bytes("node.voting.power.stake.maximum")); + rocketNetworkSnapshots.push(snapshotKey, uint32(block.number), 1.5 ether); + + // Add node count snapshot entry + AddressSetStorageInterface addressSetStorage = AddressSetStorageInterface(getContractAddress("addressSetStorage")); + bytes32 nodeIndexKey = keccak256(abi.encodePacked("nodes.index")); + rocketNetworkSnapshots.push(keccak256(abi.encodePacked("node.count")), uint32(block.number), uint224(addressSetStorage.getCount(nodeIndexKey))); // Set a protocol version value in storage for convenience with bindings setString(keccak256(abi.encodePacked("protocol.version")), "1.3.0"); diff --git a/contracts/interface/dao/protocol/RocketDAOProtocolInterface.sol b/contracts/interface/dao/protocol/RocketDAOProtocolInterface.sol index 15e35dab..ad5c56f9 100644 --- a/contracts/interface/dao/protocol/RocketDAOProtocolInterface.sol +++ b/contracts/interface/dao/protocol/RocketDAOProtocolInterface.sol @@ -19,4 +19,5 @@ interface RocketDAOProtocolInterface { function bootstrapSecurityInvite(string memory _id, address _memberAddress) external; function bootstrapSecurityKick(address _memberAddress) external; function bootstrapDisable(bool _confirmDisableBootstrapMode) external; + function bootstrapEnableGovernance() external; } diff --git a/contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol b/contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol index 21f13181..bd8c8daa 100644 --- a/contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol +++ b/contracts/interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol @@ -9,4 +9,5 @@ interface RocketDAOProtocolSettingsNodeInterface { function getVacantMinipoolsEnabled() external view returns (bool); function getMinimumPerMinipoolStake() external view returns (uint256); function getMaximumPerMinipoolStake() external view returns (uint256); + function getMaximumStakeForVotingPower() external view returns (uint256); } diff --git a/contracts/interface/network/RocketNetworkVotingInterface.sol b/contracts/interface/network/RocketNetworkVotingInterface.sol index 418b70c2..fe947232 100644 --- a/contracts/interface/network/RocketNetworkVotingInterface.sol +++ b/contracts/interface/network/RocketNetworkVotingInterface.sol @@ -3,6 +3,7 @@ pragma solidity >0.5.0 <0.9.0; interface RocketNetworkVotingInterface { function initialiseVoting() external; + function initialiseVoting(address _delegate) external; function getVotingInitialised(address _nodeAddress) external view returns (bool); function getNodeCount(uint32 _block) external view returns (uint256); function getVotingPower(address _nodeAddress, uint32 _block) external view returns (uint256); diff --git a/test/dao/dao-protocol-tests.js b/test/dao/dao-protocol-tests.js index e355d670..ccd098d0 100644 --- a/test/dao/dao-protocol-tests.js +++ b/test/dao/dao-protocol-tests.js @@ -1,6 +1,7 @@ import { printTitle } from '../_utils/formatting'; import { shouldRevert } from '../_utils/testing'; import { + setDAOProtocolBootstrapEnableGovernance, setDaoProtocolBootstrapModeDisabled, setDAOProtocolBootstrapSecurityInvite, setDAOProtocolBootstrapSetting, setDAOProtocolBootstrapSettingMulti, @@ -196,6 +197,8 @@ export default function() { await setDAOProtocolBootstrapSetting(RocketDAOProtocolSettingsAuction, 'auction.lot.create.enabled', true, { from: owner, }); + // Enable governance so we can disable bootstrap + await setDAOProtocolBootstrapEnableGovernance({from: owner}); // Disable bootstrap mode await setDaoProtocolBootstrapModeDisabled({ from: owner, @@ -228,6 +231,8 @@ export default function() { { from: owner, }); + // Enable governance so we can disable bootstrap + await setDAOProtocolBootstrapEnableGovernance({from: owner}); // Disable bootstrap mode await setDaoProtocolBootstrapModeDisabled({ from: owner, @@ -370,9 +375,18 @@ export default function() { * Proposer */ + it(printTitle('proposer', 'can not create a proposal until enabled by guardian'), async () => { + // Setup + await mockNodeSet(); + await createNode(1, proposer); + + // Create a valid proposal + await shouldRevert(createValidProposal(), 'Was able to create proposal', 'DAO has not been enabled'); + }); it(printTitle('proposer', 'can successfully submit a proposal'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -382,6 +396,7 @@ export default function() { it(printTitle('proposer', 'can not submit a proposal with a past block'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -391,6 +406,7 @@ export default function() { it(printTitle('proposer', 'can not submit a proposal if locking is not allowed'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); await setRPLLockingAllowed(proposer, false, {from: proposer}); @@ -401,6 +417,7 @@ export default function() { it(printTitle('proposer', 'can successfully refute an invalid challenge'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -452,6 +469,7 @@ export default function() { it(printTitle('proposer', 'can successfully claim proposal bond'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -476,6 +494,7 @@ export default function() { it(printTitle('proposer', 'can successfully claim invalid challenge'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -519,6 +538,7 @@ export default function() { it(printTitle('proposer', 'can not withdraw excess RPL if it is locked'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -548,6 +568,7 @@ export default function() { it(printTitle('proposer', 'can withdraw excess RPL after it is unlocked'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -576,6 +597,7 @@ export default function() { it(printTitle('proposer', 'can not create proposal without enough RPL stake'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -588,6 +610,7 @@ export default function() { it(printTitle('proposer', 'can not create proposal with invalid leaf count'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -607,6 +630,7 @@ export default function() { it(printTitle('proposer', 'can not claim bond on defeated proposal'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -638,6 +662,7 @@ export default function() { it(printTitle('proposer', 'can not claim bond twice'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -663,6 +688,7 @@ export default function() { it(printTitle('proposer', 'can not claim reward twice'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -701,6 +727,7 @@ export default function() { it(printTitle('proposer', 'can not claim reward for unresponded index'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -732,6 +759,7 @@ export default function() { it(printTitle('proposer', 'can not claim reward for unchallenged index'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -754,6 +782,7 @@ export default function() { it(printTitle('proposer', 'can not respond to a challenge with an invalid pollard'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -792,6 +821,7 @@ export default function() { it(printTitle('proposer', 'can not respond to a challenge with an invalid leaves'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -863,6 +893,7 @@ export default function() { it(printTitle('proposer', 'can not respond to a challenge with an invalid leaves (invalid primary tree leaf hash)'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -915,6 +946,7 @@ export default function() { it(printTitle('voter', 'can vote against their delegate'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); const nodes = await mockNodeSet(); await nodeSetDelegate(nodes[1], {from: nodes[0]}); await createNode(1, proposer); @@ -938,6 +970,7 @@ export default function() { it(printTitle('voter', 'can not override vote in the same direction as their delegate'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); const nodes = await mockNodeSet(); await nodeSetDelegate(nodes[1], {from: nodes[0]}); await createNode(1, proposer); @@ -964,6 +997,7 @@ export default function() { it(printTitle('proposer', 'cannot execute a failed proposal'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -999,6 +1033,7 @@ export default function() { it(printTitle('proposer', 'can not execute a vetoed proposal but can destroy it'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1037,6 +1072,7 @@ export default function() { it(printTitle('proposer', 'can invite a security council member'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1072,6 +1108,7 @@ export default function() { it(printTitle('proposer', 'can kick a security council member'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); await setDAOProtocolBootstrapSecurityInvite("Member", securityMember1, {from: owner}); @@ -1112,6 +1149,7 @@ export default function() { it(printTitle('challenger', 'can not challenge with insufficient RPL'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1138,6 +1176,7 @@ export default function() { it(printTitle('challenger', 'can not challenge if locking RPL is not allowed'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1162,6 +1201,7 @@ export default function() { it(printTitle('challenger', 'can not challenge the same index twice'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1186,6 +1226,7 @@ export default function() { it(printTitle('challenger', 'can not challenge an index with an unchallenged parent'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1208,6 +1249,7 @@ export default function() { it(printTitle('challenger', 'can not challenge an index with greater depth than max'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1230,6 +1272,7 @@ export default function() { it(printTitle('challenger', 'can not defeat a proposal before challenge period passes'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1256,6 +1299,7 @@ export default function() { it(printTitle('challenger', 'can not challenge a defeated proposal'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1288,6 +1332,7 @@ export default function() { it(printTitle('challenger', 'can not challenge after pending state'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1313,6 +1358,7 @@ export default function() { it(printTitle('challenger', 'can not claim bond while proposal is pending'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1339,6 +1385,7 @@ export default function() { it(printTitle('challenger', 'can not claim bond on invalid index'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1373,6 +1420,7 @@ export default function() { it(printTitle('challenger', 'can not claim bond on index twice'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1407,6 +1455,7 @@ export default function() { it(printTitle('challenger', 'can claim share on defeated proposal'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1456,6 +1505,7 @@ export default function() { it(printTitle('challenger', 'can recover bond if index was not used'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1498,6 +1548,7 @@ export default function() { it(printTitle('challenger', 'can recover bond if proposal was successful'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1533,6 +1584,7 @@ export default function() { it(printTitle('challenger', 'can not create challenge with proof from a deeper index'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1562,6 +1614,7 @@ export default function() { it(printTitle('other', 'can not claim reward on challenge they did not make'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1596,6 +1649,7 @@ export default function() { it(printTitle('other', 'can not claim bond on a proposal they did not make'), async () => { // Setup + await setDAOProtocolBootstrapEnableGovernance(); await mockNodeSet(); await createNode(1, proposer); @@ -1618,6 +1672,15 @@ export default function() { // Claim bond on invalid index await shouldRevert(daoProtocolClaimBondProposer(propId, [1], { from: node2 }), 'Was able to claim proposal bond', 'Not proposer'); }); + + it(printTitle('proposer', 'can disable bootstrap after enabling DAO'), async () => { + // Should fail before enabling governance + await shouldRevert(setDaoProtocolBootstrapModeDisabled({ from: owner }), 'Was able to disable bootstrap', 'On-chain governance must be enabled first'); + // Enable governance + await setDAOProtocolBootstrapEnableGovernance(); + // Should succeed + await setDaoProtocolBootstrapModeDisabled({ from: owner }); + }); }); } diff --git a/test/dao/scenario-dao-protocol-bootstrap.js b/test/dao/scenario-dao-protocol-bootstrap.js index 181ad97e..aac84ba3 100644 --- a/test/dao/scenario-dao-protocol-bootstrap.js +++ b/test/dao/scenario-dao-protocol-bootstrap.js @@ -305,6 +305,13 @@ export async function setDAOProtocolBootstrapSettingMulti(_settingContractInstan } } +export async function setDAOProtocolBootstrapEnableGovernance(txOptions) { + // Load contracts + const rocketDAOProtocol = (await upgradeExecuted()) ? await RocketDAOProtocolNew.deployed() : await RocketDAOProtocol.deployed(); + // Execute enable transaction + await rocketDAOProtocol.bootstrapEnableGovernance(); +} + /*** Security council *******/ // Use bootstrap power to invite a member to the security council