Skip to content

Commit

Permalink
Handle delegate voting power in phase 2 correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
kanewallmann committed May 15, 2024
1 parent e4fc9c8 commit 7161d1c
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 12 deletions.
28 changes: 20 additions & 8 deletions contracts/contract/dao/protocol/RocketDAOProtocolProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ contract RocketDAOProtocolProposal is RocketBase, RocketDAOProtocolProposalInter
RocketDAOProtocolVerifierInterface rocketDAOProtocolVerifier = RocketDAOProtocolVerifierInterface(getContractAddress("rocketDAOProtocolVerifier"));
require(rocketDAOProtocolVerifier.verifyVote(msg.sender, _nodeIndex, _proposalID, _votingPower, _witness), "Invalid proof");
// Apply vote
_vote(msg.sender, _votingPower, _proposalID, _voteDirection);
_vote(msg.sender, _votingPower, _proposalID, _voteDirection, true);
}

/// @notice Applies a vote during phase 2 (can be used to override vote direction of delegate)
Expand All @@ -88,15 +88,16 @@ contract RocketDAOProtocolProposal is RocketBase, RocketDAOProtocolProposalInter
uint32 blockNumber = uint32(getProposalBlock(_proposalID));
uint256 votingPower = rocketNetworkVoting.getVotingPower(msg.sender, blockNumber);
address delegate = rocketNetworkVoting.getDelegate(msg.sender, blockNumber);
// Get the vote direction of their delegate
VoteDirection delegateVote = getReceiptDirection(_proposalID, delegate);
require (delegateVote != _voteDirection, "Vote direction is the same as delegate");
// Reverse the delegate's vote
if (delegateVote != VoteDirection.NoVote) {
// Check if delegate voted in phase 1
if (getReceiptHasVotedPhase1(_proposalID, delegate)) {
// Get the vote direction of their delegate
VoteDirection delegateVote = getReceiptDirection(_proposalID, delegate);
require (delegateVote != _voteDirection, "Vote direction is the same as delegate");
// Reverse the delegate's vote
_overrideVote(delegate, msg.sender, _proposalID, votingPower, delegateVote);
}
// Apply this voter's vote
_vote(msg.sender, votingPower, _proposalID, _voteDirection);
_vote(msg.sender, votingPower, _proposalID, _voteDirection, false);
}

/// @notice Finalises a vetoed proposal by burning the proposer's bond
Expand Down Expand Up @@ -278,6 +279,13 @@ contract RocketDAOProtocolProposal is RocketBase, RocketDAOProtocolProposalInter
return getReceiptDirection(_proposalID, _nodeAddress) != VoteDirection.NoVote;
}

/// @notice Returns true if this proposal has been voted on in phase 1 by a node
/// @param _proposalID The ID of the proposal to query
/// @param _nodeAddress The node operator address to query
function getReceiptHasVotedPhase1(uint256 _proposalID, address _nodeAddress) override public view returns (bool) {
return getBool(keccak256(abi.encodePacked(daoProposalNameSpace, "receipt.phase1", _proposalID, _nodeAddress)));
}

/// @notice Returns the direction a node voted on a given proposal
/// @param _proposalID The ID of the proposal to query
/// @param _nodeAddress The node operator address to query
Expand Down Expand Up @@ -429,7 +437,7 @@ contract RocketDAOProtocolProposal is RocketBase, RocketDAOProtocolProposalInter
}

/// @dev Internal method to apply voting power against a proposal
function _vote(address _nodeOperator, uint256 _votes, uint256 _proposalID, VoteDirection _voteDirection) internal {
function _vote(address _nodeOperator, uint256 _votes, uint256 _proposalID, VoteDirection _voteDirection, bool _phase1) internal {
// Has this node already voted on this proposal?
require(!getReceiptHasVoted(_proposalID, _nodeOperator), "Node operator has already voted on proposal");
// Add votes to proposal
Expand All @@ -446,6 +454,10 @@ contract RocketDAOProtocolProposal is RocketBase, RocketDAOProtocolProposalInter
// Record the vote receipt now
setUint(keccak256(abi.encodePacked(daoProposalNameSpace, "receipt.votes", _proposalID, _nodeOperator)), _votes);
setUint(keccak256(abi.encodePacked(daoProposalNameSpace, "receipt.direction", _proposalID, _nodeOperator)), uint256(_voteDirection));
// Record delegate voted in phase 1
if (_phase1) {
setBool(keccak256(abi.encodePacked(daoProposalNameSpace, "receipt.phase1", _proposalID, _nodeOperator)), true);
}
// Log it
emit ProposalVoted(_proposalID, _nodeOperator, _voteDirection, _votes, block.timestamp);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ interface RocketDAOProtocolProposalInterface {
function getVetoed(uint256 _proposalID) external view returns (bool);
function getPayload(uint256 _proposalID) external view returns (bytes memory);
function getReceiptHasVoted(uint256 _proposalID, address _nodeAddress) external view returns (bool);
function getReceiptHasVotedPhase1(uint256 _proposalID, address _nodeAddress) external view returns (bool);
function getReceiptDirection(uint256 _proposalID, address _nodeAddress) external view returns (VoteDirection);
function getState(uint256 _proposalID) external view returns (ProposalState);

Expand Down
50 changes: 50 additions & 0 deletions test/dao/dao-protocol-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,56 @@ export default function() {
await daoProtocolOverrideVote(propId, voteStates.For, {from: nodes[0]});
});


it(printTitle('voter', 'can vote in same direction as delegate if both voting in phase 2'), async () => {
// Setup
await setDAOProtocolBootstrapEnableGovernance();
const nodes = await mockNodeSet();
await nodeSetDelegate(nodes[1], {from: nodes[0]});
await createNode(1, proposer);

// Create a valid proposal
const { propId, leaves } = await createValidProposal();

// Wait for proposal wait period to end
await increaseTime(hre.web3, voteDelayTime + 1);

// Skip phase 1 of the voting period
await increaseTime(hre.web3, votePhase1Time + 1);

// Vote for from delegate
await daoProtocolOverrideVote(propId, voteStates.For, {from: nodes[1]});

// Vote for from node
await daoProtocolOverrideVote(propId, voteStates.For, {from: nodes[0]});
});


it(printTitle('voter', 'can not vote in phase 1 then in phase 2'), async () => {
// Setup
await setDAOProtocolBootstrapEnableGovernance();
const nodes = await mockNodeSet();
await nodeSetDelegate(nodes[1], {from: nodes[0]});
await createNode(1, proposer);

// Create a valid proposal
const { propId, leaves } = await createValidProposal();

// Wait for proposal wait period to end
await increaseTime(hre.web3, voteDelayTime + 1);

// Vote as a delegate
const nodeIndex = nodeMap[nodes[0]];
const voteProof = daoProtocolGenerateVoteProof(leaves, nodeIndex);
await daoProtocolVote(propId, voteStates.For, voteProof.sum, nodeIndex, voteProof.witness, {from: nodes[0]});

// Skip phase 1 of the voting period
await increaseTime(hre.web3, votePhase1Time + 1);

// Try to override own vote
await shouldRevert(daoProtocolOverrideVote(propId, voteStates.Against, {from: nodes[0]}), 'Was able to override self', 'Node operator has already voted on proposal');
});

/**
* Failed Proposals
*/
Expand Down
24 changes: 20 additions & 4 deletions test/dao/scenario-dao-protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,17 +543,18 @@ export async function daoProtocolOverrideVote(_proposalID, _vote, txOptions) {
rocketDAOProtocolProposal.getVotingPowerVeto.call(_proposalID),
rocketDAOProtocolProposal.getReceiptDirection.call(_proposalID, txOptions.from),
rocketDAOProtocolProposal.getReceiptDirection.call(_proposalID, delegate),
rocketDAOProtocolProposal.getReceiptHasVotedPhase1.call(_proposalID, delegate),
]).then(
([proposalTotal, proposalState, proposalVotesFor, proposalVotesRequired, proposalVotesAgainst, proposalVotesVeto, direction, delegateDirection]) =>
({proposalTotal, proposalState, proposalVotesFor, proposalVotesRequired, proposalVotesAgainst, proposalVotesVeto, direction: direction.toNumber(), delegateDirection: delegateDirection.toNumber()})
([proposalTotal, proposalState, proposalVotesFor, proposalVotesRequired, proposalVotesAgainst, proposalVotesVeto, direction, delegateDirection, delegateVotedPhase1]) =>
({proposalTotal, proposalState, proposalVotesFor, proposalVotesRequired, proposalVotesAgainst, proposalVotesVeto, direction: direction.toNumber(), delegateDirection: delegateDirection.toNumber(), delegateVotedPhase1})
);
}

// Capture data
let ds1 = await getTxData();

// Add a new proposal
if (_vote === ds1.delegateDirection) {
if (ds1.delegateVotedPhase1 && _vote === ds1.delegateDirection) {
await shouldRevert(rocketDAOProtocolProposal.overrideVote(_proposalID, _vote, txOptions), 'Vote was accepted', 'Vote direction is the same as delegate');
return;
} else {
Expand All @@ -570,7 +571,22 @@ export async function daoProtocolOverrideVote(_proposalID, _vote, txOptions) {
let expectedForDelta, expectedAgainstDelta;
let expectedVetoDelta = '0'.BN;

if (ds1.delegateDirection === voteStates.For) {
if (!ds1.delegateVotedPhase1) {
if (_vote === voteStates.For) {
expectedForDelta = votingPower
expectedAgainstDelta = '0'.BN
} else if (_vote === voteStates.Against) {
expectedForDelta = '0'.BN
expectedAgainstDelta = votingPower
} else if (_vote === voteStates.AgainstWithVeto) {
expectedForDelta = '0'.BN
expectedAgainstDelta = votingPower
expectedVetoDelta = votingPower
} else if (_vote === voteStates.Abstain) {
expectedForDelta = '0'.BN
expectedAgainstDelta = '0'.BN
}
} else if (ds1.delegateDirection === voteStates.For) {
expectedForDelta = votingPower.neg();

if (_vote !== voteStates.Abstain) {
Expand Down

0 comments on commit 7161d1c

Please sign in to comment.